# How Task Space Handoff and Takeover Work Between Agent and User in ego-lite

> Learn how ego-lite enables task space handoff and takeover between agents and users with three core helpers. Master browser control transitions effectively.

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

---

**Task space handoff and takeover in ego-lite let an autonomous agent transfer browser control to a human user and reclaim it later through three core helpers: `handOffTaskSpace`, `takeOverTaskSpace`, and `completeTaskSpace`.**

In **ego-lite**, a *task space* is an isolated browsing context that can be owned by either the **agent** or the **user**. Ownership determines who sees the UI overlay and who can issue browser commands. The runtime in `citrolabs/ego-lite` exposes a declarative API for transferring this control, abstracting away low-level Chrome DevTools Protocol (CDP) calls. This article explains the ownership model, the three transition helpers, and how agents probe for control status.

---

## Task Space Ownership Model

Ownership is tracked as a string value with three possible states. According to [`package/ego-browser/src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) (lines 18-20), the system considers `"agent"` or `"agentDelegatedToUser"` as **agent-owned**, while `"user"` means the human controls the space:

```typescript
// From src/helpers.ts
const isAgentOwned = (space: TaskSpace) =>
  space.ownership === "agent" || space.ownership === "agentDelegatedToUser";

```

- **Agent-owned spaces**: The overlay is visible, and the agent can execute browser commands.
- **User-owned spaces**: The overlay is hidden, and the user interacts directly with the page.

---

## The Three Core Transition Helpers

All ownership transitions flow through three high-level helper functions in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts). Each wraps low-level runtime calls with space-selection logic and ownership checks.

### `handOffTaskSpace()` — Return Control to the User

When an agent needs to pause for human input, it calls `handOffTaskSpace([nameOrId])`. This helper:

1. Optionally switches to the specified space via `selectTaskSpaceIfProvided`
2. Checks if already user-owned — if so, returns `{ done: false, skipped: "user-owned" }` immediately
3. Otherwise invokes `ego.handOffTaskSpace()` to hide the overlay

From [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) (lines 26-39):

```typescript
export async function handOffTaskSpace(nameOrId?: string) {
  // ... selection logic ...
  if (space.ownership === "user") {
    return { done: false, skipped: "user-owned" as const };
  }
  await ego.handOffTaskSpace(space.id);
  return { done: true };
}

```

### `takeOverTaskSpace()` — Reclaim Control for the Agent

To resume automated work, the agent calls `takeOverTaskSpace([nameOrId])`. The helper:

1. Switches to the specified space if provided
2. Invokes `ego.takeOverTaskSpace()` to restore the overlay

From [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) (lines 47-53):

```typescript
export async function takeOverTaskSpace(nameOrId?: string) {
  await selectTaskSpaceIfProvided(nameOrId);
  await ego.takeOverTaskSpace();
}

```

User-owned spaces are automatically reclaimed when the agent takes over — no explicit handoff back from the user is required.

### `completeTaskSpace()` — End Work on a Space

This helper terminates a task space with two modes controlled by the `keep` option:

| Option | Behavior |
|--------|----------|
| `keep: true` | Page stays open for user review; skipped if already user-owned |
| `keep: false` | Space is closed entirely; agent first claims it if user-owned |

From [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) (lines 63-70 and 104-115), when `keep` is false, the helper forcibly takes ownership before closing:

```typescript
if (!keep) {
  if (space.ownership === "user") {
    await ego.takeOverTaskSpace(space.id); // reclaim before close
  }
  await ego.closeTaskSpace(space.id);
  return { done: true };
}

```

---

## Detecting Loss of Control: `probeAgentControl`

Agents need to know when a user has manually intercepted control. The runtime uses `probeAgentControl` (lines 57-71 in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)), which:

- Calls `ego.snapshot()` to test if the overlay is still active
- Catches `EgoUserControlError` (defined in [`src/ego-errors.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ego-errors.ts)) when the user has taken over
- Returns `false` for user control, `true` for agent control

This probe backs the public `waitForAgentControl` helper (lines 77-88), which polls without invoking `takeOverTaskSpace` itself:

```typescript
export async function waitForAgentControl(options?: { timeout?: number }) {
  // Polls probeAgentControl until true or timeout
}

```

---

## Practical Usage Examples

The public API surface is declared in [`src/format.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/format.ts) (lines 735-762). Here are common patterns:

```javascript
// 1. Hand current task space to user for manual inspection
const result = await taskSpaces.handOff();
// → { done: true } or { done: false, skipped: "user-owned" }

// 2. After user finishes, resume agent control
await taskSpaces.takeOver();  // overlay reappears

// 3. Clean up completely when done
await taskSpaces.complete(task.id, { keep: false });

// 4. Keep page open for user review
await taskSpaces.complete(task.id, { keep: true });
// Skipped with warning if user already owns the space

```

---

## Key Files and Architecture

| File | Responsibility |
|------|--------------|
| [`package/ego-browser/src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) | Core implementation of `handOffTaskSpace`, `takeOverTaskSpace`, `completeTaskSpace`, ownership logic, and `waitForAgentControl` |
| [`package/ego-browser/src/ego-errors.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/ego-errors.ts) | `EgoUserControlError` definition for control-loss detection |
| [`package/ego-browser/src/format.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/format.ts) | Public API surface for CLI (`taskSpaces.handOff`, `taskSpaces.takeOver`, etc.) |
| [`package/ego-browser/src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts) | Singleton runtime state (`globalThis.ego`) |
| `package/ego-browser/src/driver/*` | Low-level CDP bindings (`ego.handOffTaskSpace`, `ego.takeOverTaskSpace`, `ego.closeTaskSpace`) |

---

## Summary

- **Task space ownership** in ego-lite is binary: agent (`"agent"` or `"agentDelegatedToUser"`) versus user (`"user"`).
- **Three helpers** manage all transitions: `handOffTaskSpace()` yields control, `takeOverTaskSpace()` reclaims it, and `completeTaskSpace()` terminates with optional cleanup.
- **Idempotent safety**: `handOffTaskSpace` and `completeTaskSpace({ keep: true })` no-op gracefully when the space is already user-owned.
- **Control detection**: `probeAgentControl` uses `ego.snapshot()` and `EgoUserControlError` to detect user interception without blocking.
- **Implementation** is centralized in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) with low-level delegation to `src/driver/*` CDP bindings.

---

## Frequently Asked Questions

### How does an agent know when a user has taken control?

The agent calls `probeAgentControl` (internally), which attempts `ego.snapshot()`. If the user has taken control, this throws `EgoUserControlError` from [`src/ego-errors.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ego-errors.ts), and the probe returns `false`. The public `waitForAgentControl` helper polls this probe until control returns or a timeout expires.

### What happens if `handOffTaskSpace` is called on an already user-owned space?

The helper detects the ownership state in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) and immediately returns `{ done: false, skipped: "user-owned" }` without invoking the low-level runtime. This makes the operation safe to call repeatedly.

### Can `completeTaskSpace` close a space that the user currently owns?

Yes, but only with `keep: false`. The helper first forcibly reclaims ownership via `ego.takeOverTaskSpace()` before calling `ego.closeTaskSpace()`. With `keep: true`, the operation is skipped for user-owned spaces since the user already has the page open.

### Where is the public API for these helpers defined?

The CLI-facing API surface is declared in [`package/ego-browser/src/format.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/format.ts) (lines 735-762), which exposes methods like `taskSpaces.handOff()`, `taskSpaces.takeOver()`, and `taskSpaces.complete()` that wrap the underlying helper implementations.