# How to Implement a Custom Browser Tool in the ego-browser Learning Subsystem

> Implement a custom browser tool in ego-browser by creating a manifestjson file and a JavaScript module. Learn how to invoke your tool using site runBrowserTool without core engine changes.

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

---

**To add a custom browser tool in ego-browser, create a learning folder with a [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) declaring your tool, write a JavaScript module that exports a function, and invoke it via `site.runBrowserTool()` — no core engine modifications required.**

The **ego-browser learning subsystem** enables sites to expose *browser tools*: small JavaScript snippets that execute inside the browser context and return data to agent scripts. This article walks through the complete implementation flow based on the actual `citrolabs/ego-lite` source code.

---

## Step 1: Create the Learning Folder Structure

Every custom browser tool belongs to a **site-specific learning folder**. Create a new directory under `skills/ego-browser/learnings/<site-id>/`.

The folder name becomes your **site ID** — used for discovery and tool invocation. For example, the Google learning implementation lives at:

```

skills/ego-browser/learnings/google/

```

---

## Step 2: Declare Browser Tools in [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json)

Inside your learning folder, create a [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) with a `browserTools` object. This declares each tool's metadata and schema.

[`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) structure:

| Field | Purpose |
|-------|---------|
| `id` | Unique site identifier (match folder name) |
| `name` | Human-readable site name |
| `domains` | Host patterns for auto-discovery |
| `notes` | Optional Markdown documentation paths |
| `browserTools` | Object mapping tool names to definitions |

Each **browser tool definition** requires:

- `description` — What the tool does
- `path` — Relative path to the JS file (typically `browser-tools/<name>.js`)
- `args` — JSON schema of expected parameters
- `returns` — Description of the return value

Example manifest declaring a `fetch_title` tool:

```json
{
  "id": "example",
  "name": "Example Site",
  "domains": ["example.com", "*.example.com"],
  "notes": ["notes/overview.md"],
  "browserTools": {
    "fetch_title": {
      "description": "Read the page's `<title>` element.",
      "path": "browser-tools/fetch-title.js",
      "args": {},
      "returns": {
        "type": "string",
        "description": "The page title."
      }
    }
  }
}

```

---

## Step 3: Implement the Browser Tool

Create the JavaScript file referenced by `path`. The implementation **must export a function** (named or default) that receives arguments and returns a value or Promise.

The runtime loads this file as plain text via `loadBrowserToolSource()` and wraps it with `wrapBrowserTool()` before browser execution.

[`browser-tools/fetch-title.js`](https://github.com/citrolabs/ego-lite/blob/main/browser-tools/fetch-title.js):

```js
/**
 * Browser-tool: fetch_title
 * Returns the document title string.
 */
export async function fetchTitle() {
  return document.title;
}

```

**Key implementation rules:**

- The function runs in **actual browser context** — use standard DOM APIs (`document`, `window`, etc.)
- Async functions are fully supported
- Arguments from the agent script are injected automatically

---

## Step 4: Add Optional Documentation

Place Markdown files under `notes/` and reference them in [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json). These notes feed into `loadLearnedContext()` and appear when agents call `help()`.

Example: [`notes/overview.md`](https://github.com/citrolabs/ego-lite/blob/main/notes/overview.md) describing site-specific behavior and tool usage patterns.

---

## Step 5: Runtime Execution Flow

When an agent invokes your tool, ego-browser executes this pipeline:

```js
// Agent script
const title = await site.runBrowserTool('example', 'fetch_title');
console.log('Page title →', title);

```

Internal flow ([`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) → [`src/learning/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/index.ts)):

1. `loadBrowserToolSource(siteId, toolName)` — reads the JS file from disk
2. `wrapBrowserTool(source)` — builds an async IIFE injecting the supplied arguments
3. `cdpEval(wrapped)` — transmits the script to the browser via CDP (Chrome DevTools Protocol)

The CDP bridge lives in [`src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/cdp-eval.ts) with `cdp()`/`js()` functions handling the transport.

---

## Step 6: Validate with Built-in Testing

Run the validation suite before deployment:

```bash
npm test

# or

node --test

```

The validator ([`src/learning/validate-learning-format.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/validate-learning-format.ts)) enforces:

- Every `browserTools` entry has a valid `path`
- Referenced files exist on disk
- JSON schemas are well-formed
- Manifest structure conforms to specification

This catches wiring errors before agents attempt to use the tool.

---

## How Discovery and Loading Work

Three core mechanisms connect your tool to running agents:

| Mechanism | Location | Purpose |
|-----------|----------|---------|
| `siteSkillsForUrl()` | [`src/learning/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/index.ts) | Scans learning directories, matches URL hostnames against `domains`, returns applicable site-skills |
| `loadLearnedContext()` | [`src/learning/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/index.ts) | Aggregates notes and builds tool signatures for `help()` output |
| `runBrowserTool()` | [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) | Public API executing the six-step flow above |

---

## Summary

- **Create** a folder under `skills/ego-browser/learnings/<site-id>/`
- **Declare** tools in [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) with `browserTools` definitions
- **Implement** JavaScript modules that export functions using standard DOM APIs
- **Invoke** via `await site.runBrowserTool(siteId, toolName, args)`
- **Validate** with `npm test` to verify manifest and file integrity

No core engine changes are needed — the plugin architecture automatically discovers and loads your implementation.

---

## Frequently Asked Questions

### What arguments can a browser tool receive?

Any JSON-serializable object. Define the schema in [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) under `args`, then access properties as function parameters. The `wrapBrowserTool()` function in [`src/learning/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/index.ts) serializes and injects arguments into the browser execution context.

### Can browser tools interact with the page DOM?

Yes — they execute in the **live browser context** with full access to `document`, `window`, and all standard Web APIs. This enables extraction, manipulation, and interaction patterns that would be impossible from Node.js alone.

### How does ego-browser handle tool errors?

Uncaught exceptions in your browser tool propagate through the Promise chain. Since `wrapBrowserTool()` creates an async IIFE, errors reject the Promise returned by `site.runBrowserTool()`, allowing standard try/catch handling in agent scripts.

### What's the difference between browser tools and node tools?

**Browser tools** run inside the browser via CDP (defined in [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) with `browserTools`), while **node tools** execute in the Node.js runtime on the agent side. Node tools are declared separately and processed by `runNodeSiteTool()` in the same [`src/learning/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/index.ts) module.