# How the ego-lite Learning Subsystem Discovers and Loads Site-Specific Tools

> Discover how ego-lite's learning subsystem finds and loads site-specific tools using a three-stage pipeline for Node and browser execution.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: internals
- Published: 2026-08-02

---

**The ego-lite learning subsystem uses a three-stage pipeline—discovery via `siteSkillsForUrl()`, context loading via `loadLearnedContext()`, and dynamic execution—to locate, validate, and run Node-based and browser-based tools tailored to specific websites.**

The learning subsystem in **ego-lite** is the engine that lets AI agents automatically acquire site-specific knowledge and capabilities. When an agent visits a URL, this subsystem identifies what that site can do, loads relevant documentation, and makes specialized tools available through a clean runtime API. This article walks through exactly how discovery, loading, and execution work, with direct references to the source code implementation.

## Stage 1: Discovery of Matching Site Skills

The discovery process begins with `siteSkillsForUrl(url, options)` in [`package/ego-browser/src/learning/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/learning/index.ts). This function returns all site-specific skills that apply to a given URL by scanning the **learnings** directory tree and matching against domain rules.

The internal flow works as follows:

- `iterLearningDirs(root)` walks each site-skill folder under the configured `siteSkillsRoot` path
- `loadLearningManifest` loads the [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) from each folder
- `urlHostname` checks whether the supplied URL's hostname matches the manifest's `domains` array

The result is an array of `LearningEntry` objects, each containing:
- Site ID and display name
- Paths to Markdown notes
- Declared `nodeTools` and `browserTools`

This design lets you organize site skills in a flat directory structure while supporting multiple domains per skill through the manifest's `domains` field.

## Stage 2: Loading Knowledge and Tool Signatures

Once candidate skills are discovered, `loadLearnedContext(url, options)`—also in [`src/learning/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/index.ts)—transforms them into an executable context.

For each matching entry, the function performs two parallel tasks:

**Knowledge aggregation**
- Reads every Markdown file under the `notes/` subdirectory
- Validates paths with `isLearningNotePath`
- Builds `LearnedKnowledgeNote` objects containing the raw content

**Tool signature compilation**
- Extracts `nodeTools` and `browserTools` from the manifest
- Converts each definition into a `LearnedToolSignature` with description, parameters, return type, and example call

The output is a `LearnedContext` object containing:
- `siteId` and `domain`
- `knowledge`: array of aggregated notes
- `tools`: map of available tool signatures

This context object is what the helper layer exposes to agents through `site.runTool` and `site.runBrowserTool`.

## Stage 3: Dynamic Import and Execution

The execution layer handles two distinct tool types with different loading strategies.

### Node Tools: Dynamic Import with Cache Busting

Node-based site tools execute in the main process via `runNodeSiteTool(siteId, toolName, args, ctx, options)`:

```js
// Located in src/learning/index.ts
await runNodeSiteTool(
  "xcom",               // siteId from manifest
  "timeline",           // tool name in nodeTools
  { limit: 10 },        // arguments object
  { user: "alice" }     // execution context
);

```

The implementation in the source code:
1. Calls `findSiteSkill(siteId)` to locate the manifest
2. Extracts the tool schema and resolves the file path via `relativeSitePath`
3. Dynamically imports with cache busting: `import(\`${pathToFileURL(toolPath).href}?t=${Date.now()}\`)`
4. Retrieves `schema.callable` and invokes it with context and arguments

The timestamp query parameter ensures that updated tool implementations are picked up without restarting the agent.

### Browser Tools: Source String Injection

Browser tools execute in the page context via `loadBrowserToolSource(siteId, toolName, options)`:

```js
// Load raw source
const src = await loadBrowserToolSource("google", "search-extract");

// Wrap and execute
const wrapped = wrapBrowserTool(src, { query: "ego-lite" });
const result = await eval(wrapped);

```

The subsystem:
- Resolves the tool file path using the same `relativeSitePath` logic
- Reads the raw JavaScript source
- `wrapBrowserTool(source, args)` embeds it in an async IIFE with argument injection
- The agent executes this via `await site.runBrowserTool(url, "toolName", args)`

This approach lets browser tools manipulate the DOM and access page globals while keeping the agent's core logic isolated.

## Validation and Safety

All manifests pass through strict schema validation before loading:

| Validator | Purpose |
|-----------|---------|
| `validateLearning` | Validates individual learning format |
| `validateLearnings` | Validates collections of learnings |
| `validateSiteSkills` | Validates complete site-skill definitions |

These functions in [`src/learning/validate-learning-format.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/validate-learning-format.ts) enforce required fields like `name`, `version`, `domains`, and tool declarations. Invalid manifests are rejected during the discovery phase, preventing malformed site skills from reaching the execution layer.

## Integration with Agent Helpers

The learning APIs surface to agents through `helperContext()` in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts). This wires the discovery and execution functions into the `site` object that agents interact with:

```js
// Agent-facing API (internally uses the learning subsystem)
await site.runTool(url, "toolName", args);        // Node tool
await site.runBrowserTool(url, "toolName", args); // Browser tool

```

The helper layer handles URL-to-skill resolution automatically, so agents don't need to manage site IDs or manifests directly.

## Complete Working Example

Here's a full workflow combining all three stages:

```js
import { 
  loadLearnedContext, 
  runNodeSiteTool,
  loadBrowserToolSource,
  wrapBrowserTool 
} from "ego-browser/learning";

// 1. Discover and load context
const ctx = await loadLearnedContext(
  "https://x.com/timeline",
  { root: "/my/agent/workspace" }
);

if (ctx.exists) {
  console.log("Site:", ctx.siteId);
  console.log("Notes:", ctx.knowledge.length);
  console.log("Tools:", Object.keys(ctx.tools));
  
  // 2. Run Node tool
  const timeline = await runNodeSiteTool(
    ctx.siteId,
    "fetch-timeline",
    { limit: 20, includeReplies: false },
    { apiKey: process.env.X_API_KEY }
  );
  
  // 3. Execute browser tool for page interaction
  const source = await loadBrowserToolSource(ctx.siteId, "infinite-scroll");
  const scrollResult = await site.runBrowserTool(
    "https://x.com/timeline",
    "infinite-scroll",
    { maxScrolls: 3 }
  );
}

```

## Key Source Files

| File | Role |
|------|------|
| [`package/ego-browser/src/learning/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/learning/index.ts) | Main API: `siteSkillsForUrl`, `loadLearnedContext`, `runNodeSiteTool` |
| [`package/ego-browser/src/learning/check-domain-learning.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/learning/check-domain-learning.ts) | Directory iteration, manifest loading, URL matching |
| [`package/ego-browser/src/learning/validate-learning-format.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/learning/validate-learning-format.ts) | Schema validation for manifests and site skills |
| [`package/ego-browser/src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) | Integration with agent helper surface |
| `skills/ego-browser/learnings/<site>/manifest.json` | Example site-skill definitions |

## Summary

The ego-lite learning subsystem implements a robust **discover-load-run** pipeline for site-specific tooling:

- **Discovery** walks the learnings directory and matches URLs to manifests via `siteSkillsForUrl()`
- **Loading** aggregates notes and compiles tool signatures through `loadLearnedContext()`
- **Execution** dynamically imports Node tools with cache busting or injects browser tool source for page-context execution
- **Validation** enforces schema compliance at every stage to prevent runtime errors

This architecture separates site-specific knowledge from agent logic, enabling extensible, maintainable automation across any web platform.

## Frequently Asked Questions

### What file structure does ego-lite expect for site skills?

Site skills live in a `learnings/` directory with one folder per site. Each folder contains a [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) declaring `name`, `domains`, `nodeTools`, `browserTools`, and a `notes/` subdirectory with Markdown documentation. The `siteSkillsRoot` configuration points to this base directory.

### How does ego-lite prevent stale tool code from being used?

For Node tools, `runNodeSiteTool()` appends `?t=${Date.now()}` to the module URL before dynamic import. This cache-busting query parameter forces Node.js to re-evaluate the module on every call. Browser tools are read fresh from disk via `loadBrowserToolSource()` with no caching layer.

### Can a single site skill match multiple domains?

Yes. The `domains` array in [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) supports multiple hostnames and patterns. The `urlHostname` utility checks whether any supplied URL matches any entry in this array, enabling one skill definition to cover subdomains, regional variants, or related services.

### What's the difference between `nodeTools` and `browserTools` in the manifest?

`nodeTools` execute in the Node.js runtime with full system access—ideal for API calls, file operations, or database queries. `browserTools` execute as JavaScript within the browser page context, giving them access to the DOM, `window` object, and page-specific globals for scraping or UI automation.