How ego-lite Handles Isolation Between Users and Agents in the Browser
ego-lite enforces isolation between users and agents through task spaces with explicit ownership flags, blocking agent commands whenever a user owns control while allowing seamless hand-offs back and forth.
The citrolabs/ego-lite project implements a robust browser isolation model that prevents AI agents from interfering with human users during shared browser sessions. At the core of this system are task spaces—discrete execution contexts that carry an ownership property determining who can execute browser commands at any moment.
How Task Space Ownership Works
Task spaces in ego-lite support three ownership states:
"agent"— The agent has full control and may execute any helper (click, type, navigate, etc.)"agentDelegatedToUser"— The agent created the space but temporarily yielded control; the agent can select the space but real actions are blocked until control returns"user"— The human user owns the space; the agent cannot issue commands requiring agent-owned control
This ownership flag is stored on the task-space object returned from the native ego runtime and is validated by every public helper before any browser-affecting operation executes.
Core Isolation Mechanisms in helpers.ts
The isolation logic lives primarily in package/ego-browser/src/helpers.ts. Each helper enforces specific ownership rules:
switchTaskSpace
Throws an error if the target space is not agent-owned, preventing the agent from accidentally switching into a user-controlled context.
// source: package/ego-browser/src/helpers.ts#L52-L63
// throws if target space ownership !== "agent"
await taskSpaces.switch(targetSpaceId);
claimTaskSpace
Converts a user-owned space into an agent-owned one before selection, enabling the agent to resume work on previously user-controlled sessions.
// source: package/ego-browser/src/helpers.ts#L24-L27
// claims ownership from "user" → "agent"
await taskSpaces.claim('user-space-id');
handOffTaskSpace
Yields control to the user and resolves with { done: false, skipped: "user-owned" } when the space is already user-owned, making hand-offs idempotent.
// source: package/ego-browser/src/helpers.ts#L26-L34
const result = await taskSpaces.handOff();
// result: { done: true } or { done: false, skipped: "user-owned" }
completeTaskSpace
Honors the keep flag: with keep: true on a user-owned space it does nothing; with keep: false it first claims the space then closes it.
// source: package/ego-browser/src/helpers.ts#L66-L77
await taskSpaces.complete({ keep: false }); // claims then closes
waitForAgentControl
Polls via harmless ego.snapshot calls until succeeding, which only happens when agent control is restored—useful for async re-acquisition flows.
// source: package/ego-browser/src/helpers.ts#L84-L95
await taskSpaces.waitForAgentControl();
Native Bridge Enforcement
When helpers execute real browser commands (click, fill, navigate), the underlying driver calls the native ego bridge, which performs a second ownership check. If the user still has control, the bridge throws EGO_TASK_SPACE_USER_IN_CONTROL. The JavaScript layer catches this in package/ego-browser/src/ego-errors.ts and surfaces it via isEgoUserControlError.
import { isEgoUserControlError } from 'ego-browser';
try {
await page.click('#submit');
} catch (err) {
if (isEgoUserControlError(err)) {
// User currently owns this space—request takeover first
}
}
Complete User-Agent Hand-Off Flow
The isolation model follows a predictable lifecycle:
- Create task space (
newTaskSpace) → ownership ="agent" - Agent executes → all helpers run normally
- Hand off to user (
handOffTaskSpace) → UI overlay removed, ownership becomes"user"or"agentDelegatedToUser" - User interacts freely → any agent helper calls are blocked at the native bridge
- Agent resumes (
takeOverTaskSpaceorclaimTaskSpace) → overlay restored, ownership returns to"agent"
This guarantees no agent command executes during active user interaction, preserving both privacy and operational safety.
Practical Implementation Example
import { taskSpaces, page } from 'ego-browser';
// 1. Create isolated agent workspace
const ts = await taskSpaces.new('research-session');
// 2. Agent performs automated work
await page.goto('https://example.com');
await page.locator('button#search').fill('query');
await page.locator('button#go').click();
// 3. Yield to user for verification/input
await taskSpaces.handOff(); // { done: true }
// ... user interacts with page freely ...
// 4. Agent must reclaim before resuming
await taskSpaces.takeOver(); // selects space, restores overlay
await page.locator('button#next').click();
// 5. Or claim a user-created space
await taskSpaces.claim('user-started-space');
await page.locator('button#admin').click();
Key Source Files Supporting Isolation
| File | Role in Isolation |
|---|---|
package/ego-browser/src/helpers.ts |
Implements switchTaskSpace, claimTaskSpace, handOffTaskSpace, takeOverTaskSpace, waitForAgentControl with ownership-policy enforcement |
package/ego-browser/src/state.ts |
Global state singleton tracking agent workspace and default timeouts for task-space operations |
package/ego-browser/src/ego-errors.ts |
isEgoUserControlError helper interpreting native "user-in-control" exceptions |
package/ego-browser/src/driver/* (e.g., nav.ts, pointer.ts) |
Low-level driver functions calling the native ego bridge, relying on upstream ownership checks |
skills/ego-browser/SKILL.md |
End-user documentation maintaining ownership policy table synchronized with implementation |
Summary
- Task spaces are the fundamental isolation boundary in ego-lite, carrying explicit
ownershipmetadata - Three ownership states (
"agent","agentDelegatedToUser","user") define who may execute browser commands - Dual enforcement occurs in JavaScript helpers (
helpers.ts) and the nativeegobridge for defense in depth - Hand-off primitives (
handOffTaskSpace,takeOverTaskSpace,claimTaskSpace) enable safe, reversible control transfers - Error handling via
isEgoUserControlErrorallows graceful degradation when isolation boundaries are hit
Frequently Asked Questions
What happens if an agent tries to click while the user owns the task space?
The native ego bridge throws EGO_TASK_SPACE_USER_IN_CONTROL, which propagates to JavaScript and can be detected via isEgoUserControlError(). The click never executes—the browser state remains unchanged.
Can a user accidentally execute agent commands?
No. The ownership model is unidirectional: user-owned spaces block agent commands, but there is no corresponding restriction on user actions in agent-owned spaces. The handOffTaskSpace helper explicitly transfers control rather than sharing it.
How does claimTaskSpace differ from takeOverTaskSpace?
takeOverTaskSpace simply selects a space without ownership verification, useful when the agent already owns it. claimTaskSpace actively converts "user" ownership to "agent", required when resuming work on a user-controlled space. Both are implemented in package/ego-browser/src/helpers.ts.
Is the ownership check performed once or on every operation?
Every operation. Helpers validate ownership before executing, and the native bridge validates again when issuing the actual browser command. This double-check prevents race conditions where ownership changes between JavaScript validation and native execution.
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 →