How Task Space Ownership Policies Affect Helper Behavior in ego-browser

Task space ownership policies in ego-browser determine which browsing contexts an agent can control, with three categories—agent, agentDelegatedToUser, and user—that gate access to navigation, DOM manipulation, and lifecycle operations.

The ego-browser runtime (from the citrolabs/ego-lite repository) implements a strict ownership model to prevent agents from interfering with user-controlled browsing sessions. This article explains how ownership policies shape helper behavior across the task space API.

Task Space Ownership Categories

The ownership system classifies every task space into one of three states defined in src/helpers.ts:

Ownership Description Agent Can Control?
agent Created by the agent; fully under agent control ✅ Yes
agentDelegatedToUser Created by agent but temporarily handed to user via GUI takeover ✅ Yes (runtime ownership retained)
user Created by the user; agent has no access until explicitly claimed ❌ No

The ownership check is centralized in a single utility function:

// src/helpers.ts
function isAgentOwned(ownership) {
  return ownership === "agent" || ownership === "agentDelegatedToUser";
}

Every helper that requires agent privileges calls isAgentOwned before proceeding.

How Ownership Policies Gate Helper Operations

switchTaskSpace: Agent-Only Access

switchTaskSpace enforces strict ownership validation. It throws an error when targeting a user-owned space:

// src/helpers.ts (lines 58-62)
if (!isAgentOwned(space.ownership)) {
  throw new Error(`switchTaskSpace requires an agent-owned task space, got ownership ${JSON.stringify(space.ownership)}`);
}

This prevents agents from hijacking user tabs. Attempting to switch to a user-owned space fails immediately with a descriptive error.

claimTaskSpace: Converting User to Agent Ownership

claimTaskSpace is the only helper that mutates ownership state. It converts a user space to agent ownership via the native bridge, then selects it:

// src/helpers.ts (lines 24-43)
const claimed = await taskSpaces.claim('my-personal-tab');

This is the entry point for agents that need to automate a pre-existing user session.

handOffTaskSpace: No-Op for User-Owned Spaces

handOffTaskSpace respects existing user control. For user-owned spaces, the helper returns early with no action:

// src/helpers.ts (lines 21-35)
// Skips user-owned spaces—user already has control

For agent-owned spaces, it invokes the native bridge (ego.handOffTaskSpace) to hide the agent overlay and transfer UI control.

completeTaskSpace: Conditional Behavior Based on Keep Flag

completeTaskSpace implements dual-path logic depending on the keep option:

Space Ownership keep: true keep: false
agent / agentDelegatedToUser Close space, keep page open Close space and page
user Skip: { done: false, skipped: "user-owned" } Claim first, then close

The source (src/helpers.ts lines 66-71) shows this conditional enforcement: preserving user control when possible, but allowing forced takeover when explicitly requested.

takeOverTaskSpace and waitForAgentControl: Bridge-Delegated Validation

takeOverTaskSpace and waitForAgentControl differ from other helpers: they do not perform ownership checks in the helper layer. Instead, they rely on the native ego bridge to surface errors when user control cannot be overridden.

This design delegates policy enforcement to the lower-level runtime, which can implement platform-specific behaviors (macOS entitlement checks, browser permission dialogs, etc.).

Practical Code Examples

Switching to an Agent-Owned Space

// Only succeeds for agent-owned spaces
try {
  const space = await taskSpaces.switch('research-123');
  console.log('Switched to:', space.name);
} catch (e) {
  console.error('Cannot switch:', e.message);
  // Error: switchTaskSpace requires an agent-owned task space...
}

Claiming User-Created Content

// Convert user-owned to agent-owned
const claimed = await taskSpaces.claim('my-personal-tab');
console.log('Claimed and selected:', claimed.id);
// Ownership now: 'agent'

Safe Hand-Off with Skip Detection

const result = await taskSpaces.handOff();
if (!result.done) {
  console.log('Skipped – already user-owned');
  // No error thrown; graceful no-op
}

Completing with User-Control Preservation

// Respect user ownership when keeping page open
const outcome = await taskSpaces.complete('analysis-42', { keep: true });
if (!outcome.done && outcome.skipped === 'user-owned') {
  console.log('User retains control; agent cleanup skipped');
}

Waiting for Control Restoration

// Polls until agentDelegatedToUser or agent ownership restored
await taskSpaces.waitForAgentControl('demo-space', { timeout: 300 });
console.log('Agent control restored, resuming automation');

Architectural Flow: From Discovery to Action

The ownership policy system follows a consistent pipeline across all helpers:

  1. Discovery: listTaskSpaces() retrieves raw spaces from ego.listTaskSpaces
  2. Normalization: normalizeTaskSpace() converts JSON into uniform JS objects
  3. Ownership Decision: Helper inspects space.ownership against isAgentOwned()
  4. Action:
    • Allowed: Call native method (ego.useTaskSpace, ego.claimTaskSpace, etc.)
    • Forbidden: Throw error or return skipped result

This keeps policy enforcement colocated in src/helpers.ts while delegating state transitions to native bindings.

Key Source Files

File Purpose
src/helpers.ts Core ownership logic, all task space helpers
src/ego-errors.ts Error handling (assertNoEgoError, isEgoUserControlError)
src/format.ts Public API documentation generation for help()

Summary

  • Three ownership states (agent, agentDelegatedToUser, user) gate all task space operations in ego-browser
  • isAgentOwned() centralizes policy checks; most helpers refuse user-owned spaces
  • claimTaskSpace is the sole helper that changes ownership, converting useragent
  • handOffTaskSpace and completeTaskSpace with keep: true skip operations that would violate user control
  • Bridge-delegated helpers (takeOverTaskSpace, waitForAgentControl) rely on native runtime for enforcement

Frequently Asked Questions

Can an agent automate a tab the user opened manually?

Not directly. The agent must first call claimTaskSpace to convert the user-owned space to agent ownership. Until then, all navigation and DOM helpers throw ownership errors.

What happens if handOffTaskSpace targets a user-owned space?

Nothing. The helper returns a skipped result without error. The native bridge is never invoked because the user already retains UI control—no additional hand-off is needed.

Why do takeOverTaskSpace and waitForAgentControl skip ownership checks?

These operations are inherently about regaining control from users. The helper layer defers to the native ego bridge, which can implement platform-specific permission flows (system dialogs, timeouts) that the JavaScript layer cannot predict.

How does completeTaskSpace with keep: false handle user-owned spaces?

It claims the space first, then closes it. This ensures the agent can always tear down resources it created, even if the user briefly interacted with the tab.

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 →