How Task Space Handoff and Takeover Work Between Agent and User in Ego-lite
Ego-lite implements task space handoff and takeover through an ownership-based system where the handOffTaskSpace() helper transfers control to the user for manual intervention, while takeOverTaskSpace() returns control to the agent after completion.
Ego-lite isolates browsing contexts using task spaces, each with an ownership field that governs what operations are permitted. Understanding task space handoff and takeover is essential for building resilient agents that gracefully handle scenarios requiring human input—CAPTCHAs, password entry, or manual verification steps. This article explains the ownership model, the two core helpers, and practical patterns from the citrolabs/ego-lite source code.
Task Space Ownership Model
Every task space in Ego-lite carries an ownership property with three possible values:
agent— The AI automation layer has full controlagentDelegatedToUser— The agent temporarily surrendered controluser— The user owns the space through direct browser interaction
These states determine which helper functions succeed or fail. The ownership rules are documented in skills/ego-browser/SKILL.md lines 75-85 and enforced in package/ego-browser/src/helpers.ts.
Helper Behavior by Ownership State
| Helper | Behavior when target space is user-owned |
|---|---|
switchTaskSpace |
Throws — only agent-owned spaces allowed |
claimTaskSpace |
Claims space (ownership → agent) then selects it |
handOffTaskSpace |
Skipped — resolves { done: false, skipped: "user-owned" } |
completeTaskSpace(..., { keep: true }) |
Skipped — same as above |
completeTaskSpace(..., { keep: false }) |
Claims then closes the space |
takeOverTaskSpace / waitForAgentControl |
No ownership check — operates as-is |
This table reveals a critical design principle: most helpers aggressively protect user control, while takeOverTaskSpace assumes the native runtime will enforce permissions.
Handoff: Transferring Control to the User
The handOffTaskSpace helper suspends agent automation and grants the user direct browser access. Use this when the agent encounters a barrier it cannot overcome programmatically.
How handOffTaskSpace Works
In package/ego-browser/src/helpers.ts lines 26-40, the implementation follows this logic:
- If an identifier is provided, select the target task space
- Check current ownership
- If already
user-owned, return{ done: false, skipped: "user-owned" }immediately - If
agent-owned, invokeego.handOffTaskSpace()via the native bridge - Resolve to
{ done: true }once the overlay hides and control transfers
The return payload distinguishes actual handoff from no-op scenarios, allowing agents to skip redundant status messages.
Handoff Example: CAPTCHA Flow
// Create or retrieve a task space for the login flow
const task = await useOrCreateTaskSpace('login flow');
await openOrReuseTab('https://example.com/login', { wait: true });
// Complete automated steps
await fillInput('input[name=username]', 'alice');
await fillInput('input[name=password]', 'secret');
await click('button.login');
// Encounter CAPTCHA — hand control to user
const handoff = await handOffTaskSpace(task.id);
if (handoff.done) {
cliLog('✅ Handed off to user – please solve the captcha.');
} else {
cliLog('⚠️ Already under user control; nothing to hand off.');
}
Takeover: Regaining Agent Control
The takeOverTaskSpace helper reverses handoff, restoring the agent overlay and command capabilities. Unlike handoff, takeover performs no ownership validation in JavaScript—it delegates entirely to the native runtime.
How takeOverTaskSpace Works
From package/ego-browser/src/helpers.ts lines 45-53:
- Optionally select the specified task space by name or ID
- Call
ego.takeOverTaskSpace()through the native bridge - Resolve when the agent overlay becomes visible again
The lack of ownership checks means the call fails at the native layer if preconditions aren't met, rather than in JavaScript.
Takeover Example: Resuming After User Completion
// After user signals CAPTCHA completion
await takeOverTaskSpace(task.id); // Show agent overlay
await click('button.submit'); // Continue automation
cliLog('✅ Captcha solved, task continued.');
Error Handling for User Control Conflicts
When the user controls a space, any agent browser helper throws a "user is controlling" error. Your agent must catch this, surface it appropriately, and initiate handoff.
// Detect user control conflicts and respond gracefully
try {
await click('#continue');
} catch (err) {
if (err.message.includes('user is controlling')) {
cliLog('User has taken control – handing off.');
await handOffTaskSpace();
// Prompt user to confirm readiness, then:
// await takeOverTaskSpace();
} else {
throw err; // Re-throw unexpected errors
}
}
This pattern prevents hard failures and maintains clear communication about who holds control.
Implementation Files and Testing
The Ego-lite source provides complete reference implementations:
| File | Purpose |
|---|---|
package/ego-browser/src/helpers.ts |
Core handOffTaskSpace and takeOverTaskSpace implementations with ownership logic |
skills/ego-browser/SKILL.md |
User-facing documentation explaining task-space concepts and ownership rules |
package/ego-browser/src/state.ts |
Shared mutable runtime state for task space resolution |
package/ego-browser/src/taskspace-e2e.test.mjs |
End-to-end tests verifying handoff and takeover behavior |
The test suite in taskspace-e2e.test.mjs validates that ownership transitions occur correctly across native bridge boundaries.
Summary
- Task space ownership (
agent,agentDelegatedToUser,user) gates all browser automation helpers handOffTaskSpacetransfers control to the user, returning{ done, skipped? }to indicate outcometakeOverTaskSpacerestores agent control without JavaScript-side ownership checks- Most helpers throw when the user owns the space — catch "user is controlling" errors to trigger handoff
- The native runtime (
ego.*methods) ultimately enforces all ownership transitions
Frequently Asked Questions
What happens if I call handOffTaskSpace on an already user-owned space?
The function returns { done: false, skipped: "user-owned" } without invoking the native handoff. This idempotent behavior prevents redundant overlay toggles and lets agents avoid misleading status messages.
Can an agent force takeover from a user-owned space?
The JavaScript takeOverTaskSpace helper performs no ownership validation, but the native runtime ego.takeOverTaskSpace() may enforce restrictions. According to the source code, permission checks occur at the native bridge layer, not in helpers.ts.
How do I detect when the user has manually taken control?
Catch the "user is controlling" error from any browser helper (click, fillInput, etc.), then call handOffTaskSpace() to formalize the transition. The error string check pattern shown in the examples provides reliable detection.
What's the difference between agentDelegatedToUser and user ownership?
agentDelegatedToUser indicates the agent explicitly handed off control and can potentially reclaim it. user ownership means the user created or claimed the space independently, providing stronger isolation from agent interference.
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 →