How Ego-Browser Implements Task Space Isolation and Agent/User Ownership
Ego‑Browser enforces strict isolation between agent and user control through task spaces with explicit ownership flags, where agent‑owned spaces permit full automation and user‑owned spaces block agent commands until explicitly claimed.
The Ego‑Browser library in the citrolabs/ego‑lite repository provides a task space isolation model that prevents AI agents from hijacking user-controlled browsing sessions. Every tab or window exists within a task space—an isolated browsing context tagged with an ownership field that governs what operations are permitted. This design ensures seamless collaboration between automated agents and human users without accidental interference.
Understanding the Ownership Model in src/helpers.ts
The ownership policy is defined in src/helpers.ts (lines 35–43) with three possible states:
| Value | Meaning |
|---|---|
"agent" |
Fully agent‑controlled; any automation permitted |
"agentDelegatedToUser" |
Agent owns but temporarily handed to user; treated as agent‑owned |
"user" |
User‑controlled; agent commands blocked or require explicit claim |
The helper isAgentOwned (lines 43–45) collapses the first two cases:
// Returns true for both "agent" and "agentDelegatedToUser"
function isAgentOwned(ownership: string): boolean {
return ownership === "agent" || ownership === "agentDelegatedToUser";
}
This boolean check gates nearly every operation in the task space API.
Creating and Selecting Agent-Owned Spaces
newTaskSpace: Guaranteed Agent Ownership
The newTaskSpace function (lines 66–84) creates a fresh browsing context exclusively for the agent:
export async function newTaskSpace(name: string): Promise<void> {
await ego.createTaskSpace(name); // Native bridge call
return switchTaskSpace(name); // Auto-select the new space
}
Always returns an agent‑owned space—no ownership ambiguity.
useOrCreateTaskSpace: Smart Space Acquisition
useOrCreateTaskSpace (lines 86–114) implements the core policy engine:
- Space exists + agent‑owned → select it
- Space exists + user‑owned → select without claiming; caller must explicitly call
claimTaskSpacefor automation rights - Space does not exist → create new agent‑owned space
// Example: Workflow that handles both fresh and resumed sessions
const task = await useOrCreateTaskSpace('checkout-flow');
// Safe to automate—either newly created or already agent-owned
Switching and Claiming Spaces
switchTaskSpace: Ownership-Enforced Selection
The switchTaskSpace helper (lines 52–64) enforces the isolation boundary:
export async function switchTaskSpace(name: string): Promise<void> {
const space = await getTaskSpace(name);
if (!isAgentOwned(space.ownership)) {
throw new Error(`Cannot switch to user-owned task space "${name}"`);
}
// ...proceed with native switch
}
Attempting to switch to a user‑owned space throws immediately—no silent failures.
claimTaskSpace: Transferring Ownership
When the agent needs control of a user‑created tab, claimTaskSpace (lines 119–128) performs the transfer:
export async function claimTaskSpace(name: string): Promise<void> {
await ego.claimTaskSpace(name); // Native ownership transfer
return switchTaskSpace(name); // Now safe to select
}
Use this after useOrCreateTaskSpace selects a user‑owned space without claiming it.
Completing Spaces with Ownership-Aware Policies
completeTaskSpace (lines 74–78 and 96–115) demonstrates conditional behavior based on ownership and the keep option:
| Scenario | keep: true |
keep: false |
|---|---|---|
| Agent‑owned | Close normally | Close normally |
| User‑owned | Skip with { done: false, skipped: "user-owned" } |
Claim first, then close |
// Safe completion attempt—respects user control
const result = await completeTaskSpace('profile-page', { keep: true });
if (result.skipped === "user-owned") {
// Prompt user for permission or use keep:false to claim
}
This prevents accidental destruction of user work.
Handoff and Takeover: Collaborative Workflows
handOffTaskSpace: Yielding to the User
handOffTaskSpace (lines 119–130) enables interactive breakpoints:
export async function handOffTaskSpace(name: string): Promise<void> {
const space = await getTaskSpace(name);
if (space.ownership === "user") {
return; // Already user-controlled; nothing to do
}
await ego.handOffTaskSpace(name); // Native handoff
}
Useful for CAPTCHAs, 2FA, or manual verification steps.
takeOverTaskSpace: Resuming After Handoff
takeOverTaskSpace (lines 142–154) assumes the agent regains control—no ownership check, as the handoff was intentional:
export async function takeOverTaskSpace(name?: string): Promise<void> {
if (name) {
await switchTaskSpace(name); // Select if specified
}
await ego.takeOverTaskSpace(); // Native agent overlay restore
}
Called after the user signals completion (e.g., clicking "I'm done").
waitForAgentControl: Polling for Regained Control
waitForAgentControl (lines 77–88) implements blocking recovery:
export async function waitForAgentControl(
name: string,
opts: { interval?: number; timeout?: number } = {}
): Promise<void> {
const deadline = Date.now() + (opts.timeout ?? 60_000);
while (Date.now() < deadline) {
try {
await ego.snapshot(); // Harmless probe
return; // Success = agent has control
} catch (e) {
if (e.code !== 'EGO_TASK_SPACE_USER_IN_CONTROL') throw e;
// Expected failure—wait and retry
await sleep(opts.interval ?? 100);
}
}
throw new Error('Timeout waiting for agent control');
}
The EGO_TASK_SPACE_USER_IN_CONTROL error code becomes a controlled wait signal rather than a hard failure.
Listing and Inspecting Spaces
listTaskSpaces (lines 101–115) retrieves normalized space data from the native bridge:
export async function listTaskSpaces(): Promise<TaskSpace[]> {
const raw = await ego.listTaskSpaces();
return raw.map(normalizeSpace); // Converts native format to JS-friendly objects
}
Essential for building UIs that show ownership status or let users select spaces to claim.
Complete Workflow Example
import {
useOrCreateTaskSpace,
claimTaskSpace,
handOffTaskSpace,
takeOverTaskSpace,
waitForAgentControl,
completeTaskSpace
} from 'ego-browser';
// Phase 1: Initialize or resume checkout automation
const checkout = await useOrCreateTaskSpace('checkout-2024-06');
// Phase 2: Hit a CAPTCHA—hand to user
await handOffTaskSpace('checkout-2024-06');
// Phase 3: Poll until user finishes
await waitForAgentControl('checkout-2024-06', { interval: 10, timeout: 300 });
// Phase 4: Resume automation
await takeOverTaskSpace('checkout-2024-06');
// Phase 5: Complete and clean up
await completeTaskSpace('checkout-2024-06', { keep: false });
Key Implementation Files
-
src/helpers.ts– Core task‑space helpers and ownership logic:isAgentOwned,switchTaskSpace,useOrCreateTaskSpace,claimTaskSpace,completeTaskSpace,handOffTaskSpace,takeOverTaskSpace,waitForAgentControl,listTaskSpaces -
src/state.ts– Global runtime state including native bridge reference -
src/taskspace-e2e.test.mjs– End‑to‑end tests verifying ownership transitions -
skills/ego-browser/SKILL.md– User‑facing command documentation
Summary
-
Ownership is explicit: Every task space carries an
"agent","agentDelegatedToUser", or"user"flag insrc/helpers.ts -
Agent commands are gated:
switchTaskSpacethrows on user‑owned spaces;useOrCreateTaskSpacerespects existing ownership -
Ownership transfers require intent:
claimTaskSpacemust be called explicitly—no automatic hijacking -
Collaborative handoffs are first‑class:
handOffTaskSpaceandtakeOverTaskSpaceenable CAPTCHA flows and manual interruptions -
Polling recovery is built‑in:
waitForAgentControlturns ownership errors into wait loops with configurable timeouts
Frequently Asked Questions
What happens if an agent tries to automate a user‑owned task space?
The operation fails. switchTaskSpace throws an error, completeTaskSpace with keep: true returns { done: false, skipped: "user-owned" }, and most automation commands error or hang. The agent must either call claimTaskSpace to request ownership transfer or prompt the user for confirmation.
Can a user take back control after an agent has claimed a space?
Yes. The user can always seize control through the native browser UI—this changes the space's ownership to "user". The agent detects this via EGO_TASK_SPACE_USER_IN_CONTROL errors. To resume gracefully, the agent calls takeOverTaskSpace after the user signals they're done, or waitForAgentControl to poll for availability.
What's the difference between "agent" and "agentDelegatedToUser" ownership?
Both are treated as agent‑owned by isAgentOwned, meaning the agent retains full rights. The distinction tracks whether the space was explicitly handed off to the user temporarily. This flag enables the native UI to show appropriate status indicators without affecting permission checks.
How does waitForAgentControl avoid infinite loops?
It accepts timeout and interval options (default 60 seconds, 100ms polling). The probe uses ego.snapshot(), which fails with EGO_TASK_SPACE_USER_IN_CONTROL for user‑owned spaces. Only this specific error triggers a retry; other errors propagate immediately. Once snapshot() succeeds, the loop exits.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →