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

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 lines 75-85 and enforced in 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 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

// 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 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

// 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.

// 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 Core handOffTaskSpace and takeOverTaskSpace implementations with ownership logic
skills/ego-browser/SKILL.md User-facing documentation explaining task-space concepts and ownership rules
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.

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.

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 →