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:
- Optionally switches to the specified space via
selectTaskSpaceIfProvided - Checks if already user-owned — if so, returns
{ done: false, skipped: "user-owned" }immediately - 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:
- Switches to the specified space if provided
- 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 insrc/ego-errors.ts) when the user has taken over - Returns
falsefor user control,truefor 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, andcompleteTaskSpace()terminates with optional cleanup. - Idempotent safety:
handOffTaskSpaceandcompleteTaskSpace({ keep: true })no-op gracefully when the space is already user-owned. - Control detection:
probeAgentControlusesego.snapshot()andEgoUserControlErrorto detect user interception without blocking. - Implementation is centralized in
src/helpers.tswith low-level delegation tosrc/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →