How Agent-User Control Handoff Works in Ego-Lite Task Spaces
Ego-Lite manages agent-user control transfers through an ownership flag on task spaces, where the handOffTaskSpace function in src/helpers.ts skips handoff if the space is already user-owned or delegates control via the native ego.handOffTaskSpace API when the agent currently owns the space.
Ego-Lite isolates browser interactions inside dedicated task spaces that track whether the agent or user currently controls the session. Understanding the control handoff flow is essential for building automation scripts that safely transition between automated actions and manual user intervention. This article examines the ownership model and helper functions defined in the citrolabs/ego-lite repository that coordinate these transitions.
Understanding Task Space Ownership
Each task space in Ego-Lite maintains an ownership flag that dictates control permissions. The three possible states are:
"agent"– The automation script has full control"agentDelegatedToUser"– The agent initiated handoff but retains metadata ownership"user"– The user has taken manual control
According to the ownership policy table in src/helpers.ts (lines 18-31), helpers behave differently depending on the current ownership state. The handOffTaskSpace helper specifically checks for "user" ownership and returns early if the user already controls the space, preventing redundant handoff attempts.
The Handoff Implementation in helpers.ts
The core handoff logic resides in handOffTaskSpace (src/helpers.ts, lines 26-40). This async function orchestrates the transfer through a strict validation sequence:
- Verify native API availability – Confirms
globalThis.ego.handOffTaskSpaceexists - Resolve target space – Uses
findTaskSpaceto locate the space by name or ID - Ownership validation – Returns
{ done: false, skipped: "user-owned" }if the space is user-controlled - Space selection – Calls
selectTaskSpaceto ensure the correct context is active - Native handoff – Invokes
ego.handOffTaskSpace()to hide the agent overlay - Completion signal – Returns
{ done: true }to confirm successful transfer
// handOffTaskSpace implementation – see helpers.ts lines 26-40
export async function handOffTaskSpace(nameOrId?: string | number) {
const ego = globalThis.ego;
if (!ego || typeof ego.handOffTaskSpace !== "function") {
throw new Error("handOffTaskSpace requires ego.handOffTaskSpace");
}
if (nameOrId !== undefined) {
const match = await findTaskSpace(nameOrId);
if (match.ownership === "user") {
return { done: false, skipped: "user-owned" as const };
}
await selectTaskSpace(ego, match, "handOffTaskSpace");
}
assertNoEgoError(await ego.handOffTaskSpace(), "handOffTaskSpace");
return { done: true };
}
The function leverages assertNoEgoError from src/ego-errors.ts to handle runtime exceptions, ensuring that Ego-specific errors (such as isEgoUserControlError) are properly caught and reported.
Task Space Management API
Ego-Lite exposes handoff functionality through a façade pattern defined in createTaskSpacesFacade (src/helpers.ts, lines 85-96). This abstraction provides a clean interface for scripts while hiding internal resolution logic:
// taskSpaces façade – see helpers.ts lines 85-96
function createTaskSpacesFacade() {
return {
list: listTaskSpaces,
switch: switchTaskSpace,
new: newTaskSpace,
useOrCreate: useOrCreateTaskSpace,
claim: claimTaskSpace,
complete: completeTaskSpace,
handOff: handOffTaskSpace,
takeOver: takeOverTaskSpace,
waitForAgentControl,
};
}
Scripts access handoff capabilities via taskSpaces.handOff(), which maps directly to the handOffTaskSpace implementation. The façade also exposes claim for transferring user-owned spaces to the agent, and takeOver for forceful control reclamation.
Regaining Agent Control
After handing off to the user, the agent must detect when control returns. Ego-Lite provides two mechanisms in src/helpers.ts:
waitForAgentControl (lines 77-89) polls harmlessly via probeAgentControl() until a snapshot succeeds, indicating the agent has regained control. Unlike takeOverTaskSpace, this function does not invoke the native takeover API; it merely waits for the user to relinquish control or for the ownership state to change.
takeOverTaskSpace performs no ownership validation and directly calls ego.takeOverTaskSpace after optionally switching to the named space. This bypasses the safety checks present in handOffTaskSpace and should be used when the agent must forcefully resume automation.
Practical Implementation Examples
Hand Off the Current Task Space
To delegate control of the active space without specifying a name:
// Example: hand off the current task space
await taskSpaces.handOff(); // no argument → current space
Handle User-Owned Spaces Gracefully
When targeting a specific space, check the result to determine if handoff occurred:
const result = await taskSpaces.handOff('my-space');
if (result.skipped === 'user-owned') {
console.log('Space already under user control – nothing to do.');
} else {
console.log('Agent has handed control to the user.');
}
Wait for User Completion
Poll for up to 5 minutes to detect when manual interaction finishes:
// Wait up to 5 minutes for the user to give control back
await taskSpaces.waitForAgentControl('my-space', { timeout: 300 });
console.log('Agent control restored – continue automation.');
Claim a User-Owned Space
When the agent must resume work on a user-controlled space, claim ownership first:
await taskSpaces.claim('my-space'); // transfers ownership to the agent
await taskSpaces.switch('my-space'); // now safe to run agent-only helpers
Key Source Files
The control handoff flow spans several modules in the citrolabs/ego-lite package:
src/helpers.ts– Central hub for all public helpers, including task-space management and the handoff implementationsrc/ego-errors.ts– Defines error-handling utilities (assertNoEgoError,isEgoUserControlError) used by the handoff flowsrc/state.ts– Holds runtime state (e.g.,defaultTimeout) and providesagentWorkspace()used by task-space helperssrc/index.ts– Exposes the public API (handOffTaskSpace,claimTaskSpace, etc.) to the CLI and external modules
Summary
- Task spaces use an ownership flag to track whether the agent or user currently controls the browser session
handOffTaskSpacevalidates ownership before invoking the native API, returning early withskipped: "user-owned"when the user already has control- The native
ego.handOffTaskSpaceAPI hides the agent overlay and transfers full browser control to the user waitForAgentControlpolls harmlessly to detect when the user relinquishes control without forcing a takeoverclaimTaskSpacetransfers ownership from user to agent, whiletakeOverTaskSpacebypasses ownership checks for forceful control reclamation
Frequently Asked Questions
What happens if I call handOffTaskSpace on a user-owned task space?
The function returns immediately with { done: false, skipped: "user-owned" } without invoking the native ego.handOffTaskSpace API. This prevents redundant handoff attempts and potential race conditions when the user already controls the session.
How does the agent detect when the user has finished manual interaction?
Use waitForAgentControl to poll the task space until the ownership state changes back to agent control. This function probes via harmless snapshots rather than forcing a takeover, allowing the agent to resume automation only when the user explicitly relinquishes control or closes the manual session.
What is the difference between handOffTaskSpace and takeOverTaskSpace?
handOffTaskSpace performs strict ownership validation and only transfers control from agent to user, skipping execution if the space is already user-owned. In contrast, takeOverTaskSpace performs no ownership checks and directly invokes the native ego.takeOverTaskSpace API, making it suitable for emergency recovery or forced automation resumption regardless of current state.
Where is the taskSpaces API defined and exposed to external modules?
The façade is created in src/helpers.ts (lines 85-96) within the createTaskSpacesFacade function, which maps handOff to handOffTaskSpace and other convenience methods. This API is then exposed through src/index.ts for consumption by CLI tools and external automation scripts.
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 →