# How Ego-Browser Manages Task Space Ownership and Control Handoff Between Agents and Users

> Learn how ego-browser manages task space ownership and control handoff between agents and users using strict ownership flags and atomic helper functions to prevent race conditions.

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

---

**Ego-browser isolates every AI-agent interaction in a dedicated task space with strict ownership flags—`"agent"`, `"user"`, or `"agentDelegatedToUser"`—and provides atomic helper functions in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) to guarantee that only one party can issue browser commands at any moment, preventing race conditions during handoff.**

Ego-browser is the Chromium-based automation engine in the `citrolabs/ego-lite` repository that solves the fundamental multi-actor problem: how can an AI agent and a human user share a browser without stepping on each other’s inputs? The answer lies in a rigorous **task space ownership and control handoff** model that partitions every session into isolated browsing contexts governed by explicit state flags.

## Understanding Task Space Ownership States

Every task space in ego-browser carries an `ownership` property that acts as a mutex for browser control. The three possible states enforce a single-controller policy at all times.

### Agent Ownership

When `ownership === "agent"`, the AI has exclusive rights to create, select, close, and run Chrome DevTools Protocol (CDP) commands within the browsing context. Any user-initiated action is rejected with a "user is controlling" error, ensuring the automation script cannot be interrupted by accidental input.

### User Ownership

When `ownership === "user"`, the human operator retains full control. The agent **cannot** perform browser actions until it explicitly takes ownership via `claimTaskSpace`. The page remains visible to the user, but the agent overlay is hidden to prevent visual clutter.

### Delegated Ownership

The transitional state `ownership === "agentDelegatedToUser"` allows the agent to temporarily hand the space back to the user (for example, to solve a CAPTCHA or complete 2FA) while reserving the right to reclaim control later. During this phase, the user operates under the same rules as standard user ownership.

## The Control Handoff API

The public contract for managing these transitions lives in [`package/ego-browser/src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts). These helpers wrap the underlying `ego.*` RPC calls with ownership checks and automatic space selection.

### useOrCreateTaskSpace

The entry point for any automation workflow is `useOrCreateTaskSpace(nameOrId)`. This helper attempts to re-attach to an existing space or spin up a new Chromium context that inherits the logged-in user session. According to the source at lines 193–211, the logic branches on the current ownership:

- If the space is agent-owned, it is selected and returned for immediate use.
- If the space is user-owned, it is only selected; the agent must call `claimTaskSpace` before issuing clicks or navigation.

```javascript
const task = await useOrCreateTaskSpace('search-github');
// If task was user-owned, we cannot click yet...

```

### claimTaskSpace

To transition from user control to agent control, the runtime calls `claimTaskSpace(nameOrId)`. As implemented at lines 24–42, this function invokes `ego.claimTaskSpace(id, space.name)` to flip the ownership flag, then immediately calls `selectTaskSpace` to focus the agent overlay. This is the mandatory gateway for an agent to take over a space previously owned by the user.

```javascript
const existing = await findTaskSpace('user-search');
if (existing.ownership === 'user') {
  await claimTaskSpace(existing.id); // Now the agent can act
}
await click('a[href="/search"]');

```

### handOffTaskSpace

When the agent encounters a gate that requires human judgment—such as a login form with 2FA—it calls `handOffTaskSpace([nameOrId])`. The implementation at lines 26–40 first checks `match.ownership === "user"`; if true, it returns `{ done: false, skipped: "user-owned" }` as a no-op. Otherwise, it executes `ego.handOffTaskSpace()`, which flips the ownership to `"user"` and hides the agent overlay.

```javascript
await handOffTaskSpace(task.id);
console.log('Please complete the login, then reply "done".');

```

### takeOverTaskSpace

After the user finishes the manual step, the agent resumes control via `takeOverTaskSpace([nameOrId])`. Located at lines 47–53, this helper optionally selects the named space, then calls `ego.takeOverTaskSpace()` to restore agent ownership and reveal the automation overlay.

```javascript
await takeOverTaskSpace(task.id);
await waitForAgentControl(task.id); // Optional safety check

```

### completeTaskSpace

To tear down a session, use `completeTaskSpace(nameOrId, { keep })`. The logic at lines 63–78 handles edge cases: if `keep: true` and the space is user-owned, the function skips closure to prevent yanking control away from the user. Otherwise, it selects or claims the space, then calls `ego.completeTaskSpace()` or `ego.closeTaskSpace()` to destroy the browsing context.

```javascript
// Close immediately
await completeTaskSpace(task.id, { keep: false });

// Or leave open for the user
await completeTaskSpace(task.id, { keep: true });

```

### waitForAgentControl

For synchronization after a handoff, `waitForAgentControl(nameOrId, options)` polls until the agent regains control. Implemented around lines 64–79, it uses a snapshot probe that throws only when the user is controlling the space, allowing the agent to safely wait without busy-waiting on the main thread.

## Ownership Enforcement in Practice

The lifecycle of a typical task space follows a strict handshake protocol that prevents conflicting commands:

1. **Create or Reuse** – `useOrCreateTaskSpace` establishes the context. If the space exists and is user-owned, the agent must claim it before acting.
2. **Agent Execution** – While ownership is `"agent"`, the script runs CDP commands freely.
3. **User Intervention** – `handOffTaskSpace` transfers control for manual steps.
4. **Agent Resumption** – `takeOverTaskSpace` followed by `waitForAgentControl` ensures the user has finished before automation resumes.
5. **Teardown** – `completeTaskSpace` cleans up, respecting the `keep` flag to avoid destroying user-owned sessions.

This **task space ownership and control handoff** policy guarantees that at any moment exactly one party can issue browser commands, eliminating race conditions and "user-in-control" runtime errors.

## Complete Workflow Example

The following runnable example demonstrates the full handoff cycle, from creation through user delegation to final closure:

```javascript
// 1️⃣ Start a new agent-owned task space
const task = await useOrCreateTaskSpace('fetch-latest-issues');

// 2️⃣ Perform normal agent actions
await openOrReuseTab('https://github.com/citrolabs/ego-lite/issues');
await click('button[data-test-id="new-issue"]');

// 3️⃣ Hand off for manual login (e.g., 2FA)
await handOffTaskSpace(task.id);
console.log('Please complete the login, then reply "done".');

// 4️⃣ After user confirms, regain control
await takeOverTaskSpace(task.id);
await waitForAgentControl(task.id);

// 5️⃣ Continue automation
await type('input[name="title"]', 'Automated issue title');
await click('button[type="submit"]');

// 6️⃣ Close the space when finished
await completeTaskSpace(task.id, { keep: false });

```

## Key Source Files

The ownership model and handoff API are defined across the following locations in the `citrolabs/ego-lite` repository:

- **[`package/ego-browser/src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts)** – Public helper implementations that enforce ownership rules, hand-off, claim, and completion logic.
- **[`skills/ego-browser/SKILL.md`](https://github.com/citrolabs/ego-lite/blob/main/skills/ego-browser/SKILL.md)** – Agent-facing documentation explaining task-space flow and ownership semantics.
- **`package/ego-browser/src/taskspace-e2e.test.mjs`** – End-to-end test suite exercising task-space creation, hand-off, takeover, and completion.
- **[`package/ego-browser/src/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/index.ts)** – Runtime entry point exporting the helper functions (`useOrCreateTaskSpace`, `claimTaskSpace`, etc.).
- **[`package/ego-browser/src/env.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/env.ts)** – Workspace directory resolver where task-space state is persisted.

## Summary

- **Three ownership states** (`agent`, `user`, `agentDelegatedToUser`) ensure only one actor controls the browser context at any time.
- **Atomic handoff functions** in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) (`claimTaskSpace`, `handOffTaskSpace`, `takeOverTaskSpace`) manage transitions without race conditions.
- **Automatic enforcement** prevents the agent from issuing CDP commands while the user owns the space, and vice versa.
- **Lifecycle completion** via `completeTaskSpace` respects the `keep` option to avoid destroying user-owned sessions unexpectedly.

## Frequently Asked Questions

### What happens if an agent tries to click while the user owns the task space?

The action is rejected. When `ownership === "user"`, the agent overlay is hidden and any attempt to execute browser commands throws a "user is controlling" error. The agent must first call `claimTaskSpace` to transition ownership back to `"agent"`.

### Can multiple agents share the same task space simultaneously?

No. The ownership model is designed as a mutex. While multiple agents could theoretically connect to the same space ID, the first to claim it sets `ownership === "agent"`, and subsequent claim attempts would fail or block depending on the implementation in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts).

### How does `waitForAgentControl` know when the user has finished?

The helper polls the underlying `ego.snapshot` RPC (wrapped in a try/catch probe) until it receives a valid response indicating `ownership === "agent"` or `"agentDelegatedToUser"`. If the user is still controlling the space, the snapshot throws, and the function retries according to the provided timeout options.

### Is the task space ownership state persisted across browser restarts?

Yes. According to [`src/env.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/env.ts), task space state is stored in a resolved workspace directory on disk. The `ownership` flag and browsing context metadata survive process restarts, allowing an agent to reconnect to a space that was handed off to a user in a previous session.