# How to Implement `waitForAgentControl` for User Handoff Synchronization in ego‑browser

> Learn how to implement waitForAgentControl in ego-browser for seamless user handoff synchronization. Pause agent execution until user returns control of browsing sessions.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: how-to-guide
- Published: 2026-08-06

---

**Use `waitForAgentControl` to pause agent execution until a user returns control of a browsing session after a handoff in ego‑browser's task space system.**

The `ego-browser` runtime isolates automation scripts inside **task spaces**—dedicated browsing contexts with discrete ownership states. When an agent relinquishes control via `handOffTaskSpace`, the underlying **ego** bindings transition the overlay to user‑control mode. The `waitForAgentControl` primitive provides the synchronization mechanism to resume automation once the user completes their manual interaction.

## How `waitForAgentControl` Works Internally

The implementation resides in [`package/ego-browser/src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) and follows a polling‑based detection pattern. Here is the precise execution flow as implemented in the citrolabs/ego‑lite source code.

### Step 1: Task Space Selection

The helper first invokes `selectTaskSpaceIfProvided` to ensure subsequent operations target the correct isolated context:

```ts
await selectTaskSpaceIfProvided(ego, nameOrId, "waitForAgentControl");

```

*Source: [[`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts) line 398](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts#L398)*

### Step 2: Agent Control Probing

The private `probeAgentControl` function performs a minimal snapshot test using `ego.snapshot({maxResultLength: 1})`:

- **Success**: Snapshot returns data → agent has control → resolve immediately
- **User‑control error**: `isEgoUserControlError` returns `true` → return `false` to continue polling
- **Other errors**: Bubble up and abort the wait

```ts
async function probeAgentControl() { … }

```

*Source: [[`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts) lines 564‑572](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts#L564-L572)*

### Step 3: Configurable Polling Loop

The main loop in `waitForAgentControl` repeatedly probes at a configurable `interval` (default: 20 seconds) until success or `timeout` (default: 600 seconds) expiration:

```ts
while (true) {
    if (await probeAgentControl()) return;
    if (Date.now() >= deadline) throw new Error(`waitForAgentControl timed out after ${timeout}s`);
    await waits.waitForTimeout(interval * 1000);
}

```

*Source: [[`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts) lines 580‑607](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts#L580-L607)*

### Step 4: Handoff Integration

After `handOffTaskSpace` completes, the user operates freely. When ready, the UI or external script invokes `ego.takeOverTaskSpace()`, restoring the agent overlay. The next `probeAgentControl` iteration succeeds, unblocking the waiting script.

## Practical Implementation Patterns

### Basic Handoff and Wait Sequence

This pattern demonstrates the complete lifecycle: automation → handoff → wait → resumption:

```js
// Acquire or create a task space for the workflow
const ts = await taskSpaces.useOrCreate('order-456');

// Execute automated steps
await taskSpaces.click('#add-to-cart');
await taskSpaces.click('#checkout');

// Transfer control to the user for manual address entry
await taskSpaces.handOffTaskSpace();   // Returns {done: true}

// Block execution until user clicks "Take over" in the UI
await taskSpaces.waitForAgentControl(ts.id, { interval: 10, timeout: 300 });

// Resume automation after control restoration
await taskSpaces.click('#confirm-order');
await taskSpaces.completeTaskSpace(ts.id, { keep: true });

```

*Source: [`handOffTaskSpace` implementation, [`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts) lines 326‑339](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts#L326-L339)*

### Explicit Task Space Targeting

When working with spaces created elsewhere or referenced by ID:

```js
// Assume a user-owned space exists from prior interaction
const userSpaceId = 12;

// Agent hands off (no-op if already user-owned)
await taskSpaces.handOffTaskSpace(userSpaceId);

// Block until user manually triggers control return
await taskSpaces.waitForAgentControl(userSpaceId);

```

### Custom Polling Configuration

Adjust sensitivity for latency‑tolerant or time‑constrained scenarios:

```js
await taskSpaces.waitForAgentControl('profile-edit', {
  interval: 5,   // Probe every 5 seconds
  timeout: 120,  // Maximum 2 minutes waiting
});

```

## Why Polling Is the Required Approach

The ego runtime lacks an event‑driven "ownership change" subscription. The **read‑only** poll‑until‑snapshot‑succeeds pattern eliminates race conditions: `waitForAgentControl` never invokes `takeOverTaskSpace` itself, preserving user autonomy over when to return control. This design prevents agent overreach while maintaining deterministic synchronization.

## Key Source Files

| File | Purpose | Location |
|------|---------|----------|
| [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) | Core implementations of `handOffTaskSpace`, `takeOverTaskSpace`, `waitForAgentControl`, and `probeAgentControl` | [[`helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/helpers.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) |
| [`src/format.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/format.ts) | Public API surface registration (`taskSpaces.waitForAgentControl` entry) | [[`format.ts`](https://github.com/citrolabs/ego-lite/blob/main/format.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/format.ts) |
| [`src/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/index.ts) | Runtime exposure via `installEgoSdk` and task‑space façade registration | [[`index.ts`](https://github.com/citrolabs/ego-lite/blob/main/index.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/index.ts) |
| [`src/driver/waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/waits.ts) | `waits.waitForTimeout` implementation for polling delays | [[`waits.ts`](https://github.com/citrolabs/ego-lite/blob/main/waits.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/driver/waits.ts) |
| [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts) | Singleton runtime state and task‑space cache | [[`state.ts`](https://github.com/citrolabs/ego-lite/blob/main/state.ts)](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts) |

## Summary

- **`waitForAgentControl`** pauses agent execution until the user returns control of a task space
- **Polling mechanism**: Probes via `ego.snapshot` at configurable intervals, detecting agent control restoration without race conditions
- **Handoff pairing**: Use after `handOffTaskSpace` to implement seamless human‑in‑the‑loop workflows
- **Read‑only safety**: Never triggers `takeOverTaskSpace`, ensuring user-initiated control transitions only
- **Configurable timeouts**: Default 20‑second interval, 600‑second timeout; override per operation requirements

## Frequently Asked Questions

### What happens if the user never returns control?

`waitForAgentControl` throws a timeout error when the configured `timeout` duration elapses. The default is 600 seconds (10 minutes). Catch this error to implement fallback logic or escalation workflows.

### Can multiple agents wait on the same task space?

The ego runtime serializes access; only one agent context operates per task space. Concurrent `waitForAgentControl` calls from different agent instances would queue behind active ownership, though this pattern is discouraged—coordinate via external state instead.

### How does `waitForAgentControl` differ from `takeOverTaskSpace`?

`waitForAgentControl` is **passive**—it polls until detecting that something else (typically a UI action) has restored agent control. `takeOverTaskSpace` is **active**—it immediately attempts to seize control, failing if conditions aren't met. Use `waitForAgentControl` for cooperative handoffs; use `takeOverTaskSpace` for explicit control assertions.

### Is there a webhook or callback alternative to polling?

No. The ego runtime as implemented in citrolabs/ego‑lite does not expose ownership change events. The polling pattern in `waitForAgentControl` is the architecturally prescribed solution—minimal overhead (single‑byte snapshot probes) with guaranteed eventual consistency.