# How the Task Space Ownership Model Works in ego-browser

> Understand the ego-browser task space ownership model. Learn how it isolates browsing into agent or user-owned contexts with strict permission checks, enhancing security and control.

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

---

**The ego-browser task space ownership model isolates browsing work into separate browser contexts marked as either agent-owned or user-owned, enforcing strict permission checks in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) to prevent the AI from unintentionally controlling user-initiated sessions.**

The task space ownership model is the core security mechanism in [citrolabs/ego-lite](https://github.com/citrolabs/ego-lite) that determines whether the AI agent or the human user controls a specific browser context. Each task space carries an `ownership` property set to either `"agent"` or `"user"`, which governs which helper functions can manipulate that space. This design ensures that automated scripts never hijack manually opened pages while allowing seamless collaboration through explicit hand-off and claim operations.

## What Is the Task Space Ownership Model?

The model partitions browser instances into discrete **task spaces**, each marked with an ownership flag. When the AI creates a space via `newTaskSpace()`, the ownership is automatically set to `"agent"`, granting the script full control over creation, switching, and termination. **User-owned** spaces originate from manual browser interactions, requiring the agent to explicitly **claim** the space before any automated manipulation. This boundary prevents agents from accidentally closing tabs that the user is actively working on or taking over private browsing sessions.

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

All ownership logic is centralized in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) (lines 23–114). The system validates the `ownership` flag before executing sensitive operations, throwing descriptive errors when an agent attempts to modify user-controlled contexts.

### Switching Between Task Spaces

The `switchTaskSpace()` function strictly validates ownership before selecting a space. If the target is user-owned, the operation throws an error to prevent unauthorized context switches:

```typescript
const space = await findTaskSpace(nameOrId);
if (!isAgentOwned(space.ownership)) {
  throw new Error(
    `switchTaskSpace requires an agent‑owned task space, got ownership ${JSON.stringify(space.ownership)}`
  );
}

```

*(see lines 58–62 of [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts))*

### Creating Agent-Owned Spaces

When initializing a new context, `newTaskSpace()` automatically marks the space as agent-owned and persists it to the workspace:

```typescript
const created = normalizeTaskSpace(
  assertNoEgoError(await ego.createTaskSpace(name), "newTaskSpace")
);
taskSpaceNumericId(created, "newTaskSpace");
return selectTaskSpace(ego, created, "newTaskSpace");

```

*(see lines 71–84 of [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts))*

### Conditional Reuse with `useOrCreateTaskSpace`

The `useOrCreateTaskSpace()` helper prefers agent-owned spaces but allows read-only access to user-owned contexts without transferring control:

```typescript
if (isAgentOwned(existing.ownership)) {
  return selectTaskSpace(globalThis.ego, existing, "useOrCreateTaskSpace");
}
if (existing.ownership === "user") {
  // select it as‑is; user stays in control
  return selectTaskSpace(globalThis.ego, existing, "useOrCreateTaskSpace");
}

```

*(see lines 93–114 of [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts))*

### Claiming User-Owned Spaces

To transfer a user-owned space to the agent, `claimTaskSpace()` updates the ownership flag after lookup:

```typescript
const space = await findTaskSpace(nameOrId);
const claimed = normalizeTaskSpace(
  assertNoEgoError(await ego.claimTaskSpace(id, space.name), op)
);

```

*(see lines 24–27 and 34–42 of [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts))*

### Completing and Closing Workspaces

The `completeTaskSpace()` function respects ownership during cleanup. With `keep: false`, it automatically claims user-owned spaces before closing; with `keep: true`, it skips user-owned spaces to prevent accidental termination of active user work:

```typescript
if (options.keep) {
  if (match.ownership === "user") {
    return { done: false, skipped: "user‑owned" };
  }
  await selectTaskSpace(ego, match, "completeTaskSpace");
  await ego.completeTaskSpace();
} else {
  if (match.ownership === "user") {
    await claimResolvedTaskSpace(match, "completeTaskSpace");
  }
  await ego.closeTaskSpace();
}

```

*(see lines 71–99 of [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts))*

### Handing Off Control to Users

The `handOffTaskSpace()` function returns control to the human user. It short-circuits as a no-op if the space is already user-owned:

```typescript
if (match.ownership === "user") {
  return { done: false, skipped: "user‑owned" };
}
await selectTaskSpace(ego, match, "handOffTaskSpace");
await ego.handOffTaskSpace();

```

*(see lines 23–36 of [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts))*

## Practical Task Space Workflows

The following patterns demonstrate how the ownership model governs typical interactions between the agent and user:

```javascript
// 1️⃣ Create a new agent‑owned task space
const myTask = await taskSpaces.new('research‑task');
// → { id: 12, ownership: 'agent', … }

// 2️⃣ Switch to an existing agent‑owned space
await taskSpaces.switch(myTask.id);   // throws if the space is user‑owned

// 3️⃣ Claim a user‑owned space before working on it
await taskSpaces.claim('user‑opened‑page');   // ownership becomes 'agent'

// 4️⃣ Finish work, closing the space (keep:false) or handing it to the user (keep:true)
await taskSpaces.complete(myTask.id, { keep: false });  // closes and discards
await taskSpaces.complete(myTask.id, { keep: true });   // leaves page open for user

// 5️⃣ Hand off control to the user (no‑op if already user‑owned)
await taskSpaces.handOff(myTask.id);

// 6️⃣ Take over a space after a hand‑off
await taskSpaces.takeOver(myTask.id);

```

## Supporting Files

While [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) contains the core logic, the task space system relies on additional modules:

- **[`src/format.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/format.ts)** – Documents the public helper signatures (e.g., `taskSpaces.new`, `taskSpaces.switch`) and type definitions for the API.
- **[`src/env.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/env.ts)** – Resolves the workspace directory where task space metadata persists and manages runtime configuration paths.

## Summary

- The **task space ownership model** assigns every browser context an `ownership` flag of either `"agent"` or `"user"` to prevent unauthorized automation.
- **Agent-owned** spaces allow full CRUD operations via `newTaskSpace()`, `switchTaskSpace()`, and `completeTaskSpace()` without additional authorization checks.
- **User-owned** spaces require explicit **claiming** via `claimTaskSpace()` before the agent can modify them, ensuring manual browsing sessions remain under human control.
- The `handOffTaskSpace()` and `takeOverTaskSpace()` functions enable safe context switching between AI and human operators without data loss.
- All permission checks are centralized in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) (lines 23–114), throwing descriptive errors when ownership requirements are violated.

## Frequently Asked Questions

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

According to the source code in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) (lines 58–62), the `switchTaskSpace()` function throws an explicit `Error` stating that the operation requires an agent-owned space. The agent must first call `claimTaskSpace()` to transfer ownership before switching contexts.

### Can the agent close a user-owned task space without claiming it?

No. As implemented in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) (lines 71–99), calling `completeTaskSpace()` with `keep: false` on a user-owned space automatically invokes `claimResolvedTaskSpace()` to transfer ownership before closing. With `keep: true`, the operation returns `{ done: false, skipped: "user‑owned" }` to preserve the user's active session.

### What is the difference between `handOffTaskSpace()` and `takeOverTaskSpace()`?

**`handOffTaskSpace()`** transfers ownership from the agent back to the user, returning control of the browser context. It is a no-op if the space is already user-owned (lines 23–36). **`takeOverTaskSpace()`** allows the agent to reclaim control after a hand-off, forcing the agent overlay back onto the space without requiring manual user intervention.

### Where is the ownership state persisted in the ego-browser architecture?

While [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) manages the permission logic, [`src/env.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/env.ts) defines the workspace directory where task space metadata is stored. The ownership flag is stored on each task space object and validated in-memory by the helper functions before any state-changing operations.