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

> Learn how Ego-lite handles task space handoff and takeover using an ownership system. Understand how control shifts between agent and user for seamless task completion.

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

---

**Ego-lite implements task space handoff and takeover through an ownership-based system where the `handOffTaskSpace()` helper transfers control to the user for manual intervention, while `takeOverTaskSpace()` returns control to the agent after completion.**

Ego-lite isolates browsing contexts using **task spaces**, each with an `ownership` field that governs what operations are permitted. Understanding task space handoff and takeover is essential for building resilient agents that gracefully handle scenarios requiring human input—CAPTCHAs, password entry, or manual verification steps. This article explains the ownership model, the two core helpers, and practical patterns from the `citrolabs/ego-lite` source code.

## Task Space Ownership Model

Every task space in Ego-lite carries an `ownership` property with three possible values:

- **`agent`** — The AI automation layer has full control
- **`agentDelegatedToUser`** — The agent temporarily surrendered control
- **`user`** — The user owns the space through direct browser interaction

These states determine which helper functions succeed or fail. The ownership rules are documented in [`skills/ego-browser/SKILL.md`](https://github.com/citrolabs/ego-lite/blob/main/skills/ego-browser/SKILL.md) lines 75-85 and enforced in [`package/ego-browser/src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts).

### Helper Behavior by Ownership State

| Helper | Behavior when target space is `user`-owned |
|--------|-------------------------------------------|
| `switchTaskSpace` | **Throws** — only agent-owned spaces allowed |
| `claimTaskSpace` | Claims space (ownership → `agent`) then selects it |
| `handOffTaskSpace` | **Skipped** — resolves `{ done: false, skipped: "user-owned" }` |
| `completeTaskSpace(..., { keep: true })` | **Skipped** — same as above |
| `completeTaskSpace(..., { keep: false })` | Claims then closes the space |
| `takeOverTaskSpace` / `waitForAgentControl` | No ownership check — operates as-is |

This table reveals a critical design principle: **most helpers aggressively protect user control**, while `takeOverTaskSpace` assumes the native runtime will enforce permissions.

## Handoff: Transferring Control to the User

The **`handOffTaskSpace`** helper suspends agent automation and grants the user direct browser access. Use this when the agent encounters a barrier it cannot overcome programmatically.

### How `handOffTaskSpace` Works

In [`package/ego-browser/src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) lines 26-40, the implementation follows this logic:

1. If an identifier is provided, select the target task space
2. Check current ownership
3. If already `user`-owned, return `{ done: false, skipped: "user-owned" }` immediately
4. If `agent`-owned, invoke `ego.handOffTaskSpace()` via the native bridge
5. Resolve to `{ done: true }` once the overlay hides and control transfers

The **return payload distinguishes actual handoff from no-op scenarios**, allowing agents to skip redundant status messages.

### Handoff Example: CAPTCHA Flow

```javascript
// Create or retrieve a task space for the login flow
const task = await useOrCreateTaskSpace('login flow');
await openOrReuseTab('https://example.com/login', { wait: true });

// Complete automated steps
await fillInput('input[name=username]', 'alice');
await fillInput('input[name=password]', 'secret');
await click('button.login');

// Encounter CAPTCHA — hand control to user
const handoff = await handOffTaskSpace(task.id);

if (handoff.done) {
  cliLog('✅ Handed off to user – please solve the captcha.');
} else {
  cliLog('⚠️ Already under user control; nothing to hand off.');
}

```

## Takeover: Regaining Agent Control

The **`takeOverTaskSpace`** helper reverses handoff, restoring the agent overlay and command capabilities. Unlike handoff, **takeover performs no ownership validation in JavaScript**—it delegates entirely to the native runtime.

### How `takeOverTaskSpace` Works

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

1. Optionally select the specified task space by name or ID
2. Call `ego.takeOverTaskSpace()` through the native bridge
3. Resolve when the agent overlay becomes visible again

The lack of ownership checks means the call fails at the native layer if preconditions aren't met, rather than in JavaScript.

### Takeover Example: Resuming After User Completion

```javascript
// After user signals CAPTCHA completion
await takeOverTaskSpace(task.id);   // Show agent overlay
await click('button.submit');       // Continue automation
cliLog('✅ Captcha solved, task continued.');

```

## Error Handling for User Control Conflicts

When the user controls a space, **any agent browser helper throws a "user is controlling" error**. Your agent must catch this, surface it appropriately, and initiate handoff.

```javascript
// Detect user control conflicts and respond gracefully
try {
  await click('#continue');
} catch (err) {
  if (err.message.includes('user is controlling')) {
    cliLog('User has taken control – handing off.');
    await handOffTaskSpace();
    // Prompt user to confirm readiness, then:
    // await takeOverTaskSpace();
  } else {
    throw err;  // Re-throw unexpected errors
  }
}

```

This pattern prevents hard failures and maintains clear communication about who holds control.

## Implementation Files and Testing

The Ego-lite source provides complete reference implementations:

| File | Purpose |
|------|---------|
| [`package/ego-browser/src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) | Core `handOffTaskSpace` and `takeOverTaskSpace` implementations with ownership logic |
| [`skills/ego-browser/SKILL.md`](https://github.com/citrolabs/ego-lite/blob/main/skills/ego-browser/SKILL.md) | User-facing documentation explaining task-space concepts and ownership rules |
| [`package/ego-browser/src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts) | Shared mutable runtime state for task space resolution |
| `package/ego-browser/src/taskspace-e2e.test.mjs` | End-to-end tests verifying handoff and takeover behavior |

The test suite in `taskspace-e2e.test.mjs` validates that ownership transitions occur correctly across native bridge boundaries.

## Summary

- **Task space ownership** (`agent`, `agentDelegatedToUser`, `user`) gates all browser automation helpers
- **`handOffTaskSpace`** transfers control to the user, returning `{ done, skipped? }` to indicate outcome
- **`takeOverTaskSpace`** restores agent control without JavaScript-side ownership checks
- **Most helpers throw when the user owns the space** — catch "user is controlling" errors to trigger handoff
- The native runtime (`ego.*` methods) ultimately enforces all ownership transitions

## Frequently Asked Questions

### What happens if I call `handOffTaskSpace` on an already user-owned space?

The function returns `{ done: false, skipped: "user-owned" }` without invoking the native handoff. This idempotent behavior prevents redundant overlay toggles and lets agents avoid misleading status messages.

### Can an agent force takeover from a user-owned space?

The JavaScript `takeOverTaskSpace` helper performs no ownership validation, but the native runtime `ego.takeOverTaskSpace()` may enforce restrictions. According to the source code, permission checks occur at the native bridge layer, not in [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts).

### How do I detect when the user has manually taken control?

Catch the "user is controlling" error from any browser helper (`click`, `fillInput`, etc.), then call `handOffTaskSpace()` to formalize the transition. The error string check pattern shown in the examples provides reliable detection.

### What's the difference between `agentDelegatedToUser` and `user` ownership?

`agentDelegatedToUser` indicates the agent explicitly handed off control and can potentially reclaim it. `user` ownership means the user created or claimed the space independently, providing stronger isolation from agent interference.