# How to Extend ego-browser with Custom Agent Helpers in agent_helpers.js

> Learn how to extend ego-browser with custom agent helpers by creating an agent_helpers.js file. ego-browser automatically loads and merges your custom helper functions for enhanced agent functionality.

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

---

**Create an [`agent_helpers.js`](https://github.com/citrolabs/ego-lite/blob/main/agent_helpers.js) file in your agent's workspace directory; ego-browser automatically loads and merges its exported functions into the helper context at runtime.**

The `ego-browser` runtime from citrolabs/ego-lite provides a built-in extension system that lets you add custom functionality without modifying core source code. When you implement custom agent helpers with the [`agent_helpers.js`](https://github.com/citrolabs/ego-lite/blob/main/agent_helpers.js) extension system, your functions become first-class citizens alongside built-in helpers like `click()`, `navigate()`, and `cdp()`.

This guide covers exactly how the loading mechanism works, where to place your file, and how to structure your exports for seamless integration.

## How the agent_helpers.js Loader Works

The extension system is implemented in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) within the `helperContext()` function. At line 853, the runtime checks for the presence of [`agent_helpers.js`](https://github.com/citrolabs/ego-lite/blob/main/agent_helpers.js) in the current agent workspace and dynamically imports it:

```typescript
// src/helpers.ts (excerpt around line 853)
const path = join(state.agentWorkspace(), "agent_helpers.js");
if (await exists(path)) {
  const mod = await import(path);
  Object.assign(helpers, mod);   // merge exports into helper surface
}

```

This loading occurs **once per agent session**, immediately before your script executes. The `Object.assign()` call means both named exports and default export members become available as top-level helper functions.

The `state.agentWorkspace()` call resolves to the active task's working directory, ensuring isolation between different agent workspaces.

## File Location and Naming Requirements

Your [`agent_helpers.js`](https://github.com/citrolabs/ego-lite/blob/main/agent_helpers.js) file must follow strict placement rules:

- **Exact filename**: [`agent_helpers.js`](https://github.com/citrolabs/ego-lite/blob/main/agent_helpers.js) (case-sensitive)
- **Location**: Root of the agent workspace directory returned by `state.agentWorkspace()`
- **Format**: Standard JavaScript (ES modules) with `import`/`export` syntax

The runtime uses `join()` to construct the path, so no subdirectories or alternative naming patterns are recognized.

## Export Patterns for Custom Helpers

### Named Exports (Recommended)

Export individual functions for clear, discoverable APIs:

```javascript
// agent_helpers.js
/**
 * Returns the current page title in uppercase.
 */
export async function pageTitleUpper() {
  const title = await ego.eval(`document.title`);
  return String(title).toUpperCase();
}

/**
 * Clicks an element and waits for navigation to complete.
 */
export async function clickAndWait(selector) {
  await ego.click(selector);
  await ego.waitForNavigation({ timeout: 30 });
}

```

### Default Export (Object Pattern)

Bundle multiple helpers in a single object:

```javascript
// agent_helpers.js
export default {
  /** Returns URL without query parameters. */
  async cleanUrl() {
    const url = await ego.eval('window.location.href');
    return url.split('?')[0];
  },
  
  /** Checks if element exists with retry logic. */
  async exists(selector, maxRetries = 3) {
    for (let i = 0; i < maxRetries; i++) {
      const found = await ego.eval(`!!document.querySelector(${JSON.stringify(selector)})`);
      if (found) return true;
      await ego.sleep(500);
    }
    return false;
  }
};

```

Both patterns merge into the same helper namespace. Avoid naming collisions with built-in helpers: `click`, `navigate`, `cdp`, `eval`, `sleep`, `type`, `press`, and `waitForNavigation` are reserved.

## Using Custom Helpers in Agent Scripts

Once loaded, custom helpers are invoked identically to built-ins:

```javascript
// stdin script passed to ego-browser
await clickAndWait('a.login');
const title = await pageTitleUpper();
console.log('PAGE TITLE:', title);

const url = await cleanUrl();  // from default export
console.log('Clean URL:', url);

```

The runtime resolves these calls through the merged `helpers` object, ensuring consistent `await` semantics and error handling.

## Complete Working Example

Directory structure:

```

/workspace/task-001/
├── agent_helpers.js
└── script.js   # optional: can also pass via stdin

```

**agent_helpers.js:**

```javascript
/**
 * Smart form helper: fills inputs using label text matching.
 */
export async function fillByLabel(labelText, value) {
  const selector = await ego.eval(`
    [...document.querySelectorAll('label')]
      .find(l => l.textContent.includes(${JSON.stringify(labelText)}))
      ?.getAttribute('for')
  `);
  if (!selector) throw new Error(`Label "${labelText}" not found`);
  await ego.type(`#${selector}`, value);
}

/**
 * Extracts all links matching a domain pattern.
 */
export async function extractLinks(domainPattern) {
  const links = await ego.eval(`
    [...document.querySelectorAll('a[href]')]
      .map(a => a.href)
      .filter(href => /${domainPattern}/.test(href))
  `);
  return links;
}

```

**Invocation:**

```bash
echo 'await fillByLabel("Email", "user@example.com");' | ego-browser

```

## Runtime Behavior and Lifecycle

| Aspect | Behavior |
|--------|----------|
| Load timing | Once per session, before script execution |
| Reload trigger | New workspace initialization or task reset |
| Caching | File is re-imported fresh each session |
| Scope | Same sandbox as built-in helpers; no host filesystem access |
| Error handling | Import failures throw and terminate agent startup |

Changes to [`agent_helpers.js`](https://github.com/citrolabs/ego-lite/blob/main/agent_helpers.js) require a new session to take effect—there is no hot-reload mechanism.

## Source Reference Files

| File | Purpose |
|------|---------|
| [`package/ego-browser/src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) | Core loader implementation at line 853 |
| [`AGENTS.md`](https://github.com/citrolabs/ego-lite/blob/main/AGENTS.md) | High-level documentation of the helper system |
| `package/ego-browser/src/helpers.test.mjs` | Test coverage for custom helper loading |

## Summary

- **Place** [`agent_helpers.js`](https://github.com/citrolabs/ego-lite/blob/main/agent_helpers.js) in the root of your agent workspace directory
- **Export** functions using named exports or default export object patterns
- **Access** custom helpers directly by name with standard `await` syntax
- **Reload** happens automatically on new sessions—modify, then restart

The extension system in ego-browser transforms [`agent_helpers.js`](https://github.com/citrolabs/ego-lite/blob/main/agent_helpers.js) from a simple module into an integrated part of the agent's capability surface, maintaining the same security boundaries and ergonomic patterns as core functionality.

## Frequently Asked Questions

### Can I use TypeScript for agent_helpers.js?

No—the runtime specifically looks for [`agent_helpers.js`](https://github.com/citrolabs/ego-lite/blob/main/agent_helpers.js) and uses dynamic `import()` on that exact filename. You must transpile TypeScript to JavaScript before deployment. The source analysis shows no `.ts` extension handling in the loader logic.

### What happens if my helper name conflicts with a built-in?

The `Object.assign(helpers, mod)` call at line 853 means your export **overwrites** the built-in. This is powerful but dangerous—avoid redefining core helpers like `click` or `navigate` unless intentionally patching behavior.

### Can I import external npm packages in agent_helpers.js?

Only if the package is pre-installed in the workspace and resolvable by Node.js's module resolution. The dynamic `import()` runs in the same context as the main process, but network-dependent imports may fail in sandboxed environments. Stick to built-in Node.js modules for reliability.

### How do I debug a helper that isn't loading?

Verify three things: (1) exact filename [`agent_helpers.js`](https://github.com/citrolabs/ego-lite/blob/main/agent_helpers.js) in workspace root, (2) valid ES module syntax (no `require()`), and (3) no syntax errors preventing `import()`. Add `console.log` at the top level of [`agent_helpers.js`](https://github.com/citrolabs/ego-lite/blob/main/agent_helpers.js)—output appears in agent logs if loading succeeds.