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

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 (lines 18-20), the system considers "agent" or "agentDelegatedToUser" as agent-owned, while "user" means the human controls the space:

// 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. 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 (lines 26-39):

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 (lines 47-53):

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 (lines 63-70 and 104-115), when keep is false, the helper forcibly takes ownership before closing:

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), which:

  • Calls ego.snapshot() to test if the overlay is still active
  • Catches EgoUserControlError (defined in 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:

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 (lines 735-762). Here are common patterns:

// 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 Core implementation of handOffTaskSpace, takeOverTaskSpace, completeTaskSpace, ownership logic, and waitForAgentControl
package/ego-browser/src/ego-errors.ts EgoUserControlError definition for control-loss detection
package/ego-browser/src/format.ts Public API surface for CLI (taskSpaces.handOff, taskSpaces.takeOver, etc.)
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 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, 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 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 (lines 735-762), which exposes methods like taskSpaces.handOff(), taskSpaces.takeOver(), and taskSpaces.complete() that wrap the underlying helper implementations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →