# How Site-Specific Learnings Are Loaded from the Skills Directory in ego-lite

> Discover how ego-lite loads site-specific learnings from the skills directory. Understand the runtime process for domain-specific capabilities in this technical guide.

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

---

**Ego-lite loads site-specific learnings (notes, tool definitions, and manifests) from `skills/ego-browser/learnings/` at runtime through a dedicated learning subsystem that discovers, validates, and executes domain-specific capabilities.**

The **ego-lite** open-source browser automation framework extends agent capabilities through modular site skills. These learnings live in a structured directory tree and are dynamically discovered based on the current URL, letting agents access domain knowledge and custom tools without hardcoding site logic into the core engine.

## Discovery: How ego-lite Finds Relevant Site Skills

The entry point for loading site-specific learnings is `siteSkillsForUrl()` 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 resolves the **site-skills root** from the agent workspace and searches for matching manifests.

```ts
// From package/ego-browser/src/learning/index.ts
export async function siteSkillsForUrl(url, { agentWorkspace }) {
  const siteSkillsRoot = path.join(agentWorkspace, 'skills', 'ego-browser', 'learnings');
  // ...looks for a manifest whose domain matches the URL
}

```

The discovery chain works as follows:

- **`siteSkillsForUrl()`** – public API exposed via the `site` helper
- **`siteSkillsForUrlCore()`** – core implementation that scans `siteSkillsRoot`
- **`iterLearningDirs()`** – traverses the `skills/ego-browser/learnings/` directory
- **`loadLearningManifest()`** – parses each `<site>/manifest.json`

Domain matching logic in [`src/learning/check-domain-learning.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/check-domain-learning.ts) determines whether a site's manifest applies to the current URL based on domain patterns declared in the manifest.

## Manifest Loading: The Structure of Site Learnings

Each site skill resides in its own subdirectory with a **manifest.json** file that declares available capabilities. The manifest schema includes:

- **`id`** and **`name`** – site identifier and display name
- **`nodeTools`** – paths to Node.js executable scripts
- **`browserTools`** – paths to browser-executed scripts
- **`notes`** – markdown files containing domain knowledge

Example manifest structure from [`skills/ego-browser/learnings/x-com/manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/skills/ego-browser/learnings/x-com/manifest.json):

```json
{
  "id": "x-com",
  "name": "X (Twitter)",
  "domain": "x.com",
  "nodeTools": ["tools/search-users.js"],
  "browserTools": ["tools/extract-timeline.js"],
  "notes": ["notes/rate-limits.md", "notes/api-conventions.md"]
}

```

Validation occurs through [`src/learning/validate-learning-format.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/validate-learning-format.ts), which ensures manifests conform to the expected schema before loading proceeds.

## Context Assembly: Building the LearnedContext Object

Once a matching manifest is found, `loadLearnedContext()` (exposed as `site.learnContext`) assembles all relevant information into a structured **LearnedContext** object.

For each site entry, the system:

1. Reads every note file under `<site>/notes/*.md` via `fs.readFile`
2. Constructs **tool signatures** for both Node and browser tools
3. Attaches ready-to-run examples using `await site.runTool(...)` or `await site.runBrowserTool(...)`

The resulting `LearnedContext` contains:

| Property | Description |
|----------|-------------|
| `exists` | Boolean indicating whether a skill was found |
| `siteId`, `siteName`, `domain` | Identification metadata |
| `knowledge` | Array of `{ siteId, fileName, content }` objects from note files |
| `tools` | Array of `{ siteId, toolName, toolType, description, args, returns, example }` objects |

```ts
// Using the loaded context in an ego-browser script
const ctx = await site.learnContext();   // Uses current page URL
if (ctx.exists) {
  console.log('Site:', ctx.siteName);
  console.log('Notes:', ctx.knowledge.map(k => k.fileName));
  console.log('Available tools:', ctx.tools.map(t => t.toolName));
}

```

## Tool Execution: Running Node and Browser Tools

The learning subsystem supports two execution environments for site tools.

### Node Tools: Server-Side Execution

**`runNodeSiteTool()`** in [`src/learning/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/index.ts) handles Node-side tool execution:

```ts
// Executes skills/ego-browser/learnings/x-com/tools/search-users.js
const result = await site.runTool('x-com', 'search-users', {
  query: 'john doe'
});
console.log(result);

```

Implementation details from the source:

- Locates tool file using `relativeSitePath()`
- Dynamically imports the module
- Retrieves the declared callable function
- Invokes it with the helper context

### Browser Tools: In-Page Execution

**`runSiteBrowserTool()`** loads and executes code inside the actual browser page:

```ts
// Runs a browser-side extraction tool
await site.runBrowserTool('google', 'search-extract', {
  query: 'openai gpt-4'
});

```

The execution flow:

- **`loadBrowserToolSource()`** fetches raw source as a string
- **`wrapBrowserTool()`** adds necessary wrappers for the execution environment
- **`evaluate()`** injects and runs the code in the page context

## Helper Facade: The `site` Namespace

All learning functionality is exposed through the **helper context** defined in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts). The `createSiteFacade()` function injects the `site` namespace into agent scripts:

```ts
export function helperContext(extra = {}) {
  return {
    page: createPageFacade(),
    browser: createBrowserFacade(),
    site: {
      skills: siteSkills,
      skillsForUrl: siteSkillsForUrl,
      runTool: runSiteTool,
      runBrowserTool: runSiteBrowserTool,
      learnContext,
    },
    // …other helpers
  };
}

```

This facade pattern ensures agents interact with a clean, promise-based API regardless of the underlying file system operations and module loading complexity.

## Complete Workflow Example

```ts
// Full workflow: discover, load context, and execute tools
async function interactWithSite(url) {
  // Discovery phase
  const skills = await site.skillsForUrl(url);
  console.log('Matching site IDs:', skills.map(s => s.id));

  // Context loading
  const ctx = await site.learnContext();
  if (!ctx.exists) {
    console.log('No site-specific learning available');
    return;
  }

  // Tool execution based on available capabilities
  if (ctx.tools.some(t => t.toolName === 'search-users')) {
    const users = await site.runTool(ctx.siteId, 'search-users', { query: 'ego-lite' });
    console.log('Found users:', users);
  }
}

```

## Key Files in the Learning Subsystem

| Purpose | File Path |
|---------|-----------|
| Core learning API (`loadLearnedContext`, `runNodeSiteTool`, `loadBrowserToolSource`) | [`package/ego-browser/src/learning/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/learning/index.ts) |
| Manifest validation and schema utilities | [`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) |
| Domain matching and directory traversal | [`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) |
| Helper facade with `site` namespace | [`package/ego-browser/src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) |
| Example manifest (X/Twitter) | [`skills/ego-browser/learnings/x-com/manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/skills/ego-browser/learnings/x-com/manifest.json) |
| Example Node tool | [`skills/ego-browser/learnings/x-com/tools/search-users.js`](https://github.com/citrolabs/ego-lite/blob/main/skills/ego-browser/learnings/x-com/tools/search-users.js) |
| Example Browser tool | [`skills/ego-browser/learnings/google/tools/search-extract.js`](https://github.com/citrolabs/ego-lite/blob/main/skills/ego-browser/learnings/google/tools/search-extract.js) |

## Summary

- **Discovery** starts at `siteSkillsForUrl()`, resolving the skills directory from `state.agentWorkspace()` and matching manifests by domain
- **Manifest loading** via `loadLearningManifest()` parses [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) files declaring tools and notes
- **Context assembly** in `loadLearnedContext()` produces structured `LearnedContext` objects with knowledge arrays and tool signatures
- **Dual execution paths** support Node tools (`runNodeSiteTool`) and browser tools (`loadBrowserToolSource` → `wrapBrowserTool` → `evaluate`)
- **Helper facade** exposes all functionality through the `site` namespace injected into agent scripts

## Frequently Asked Questions

### Where does ego-lite look for site skills by default?

The default skills root resolves to `skills/ego-browser/learnings/` relative to the agent workspace returned by `state.agentWorkspace()`. This path is constructed in `siteSkillsForUrl()` using `path.join(agentWorkspace, 'skills', 'ego-browser', 'learnings')`.

### What happens if multiple site manifests match a URL?

The `siteSkillsForUrl()` implementation returns an array of matching entries, allowing multiple skills to apply to overlapping domains. The `loadLearnedContext()` function aggregates knowledge and tools from all matches, with `siteId` disambiguating sources.

### Can I use site tools without loading the full context?

Yes. While `site.learnContext()` provides comprehensive discovery, you can call `site.runTool()` or `site.runBrowserTool()` directly with explicit site and tool names. These methods internally resolve paths using the same `siteSkillsForUrl()` discovery logic without requiring a separate context load.

### How are browser tools sandboxed during execution?

Browser tools are loaded as raw source strings via `loadBrowserToolSource()`, wrapped by `wrapBrowserTool()` to establish the execution environment, and then evaluated through the generic `evaluate()` helper. This runs within the page context but does not have direct access to the Node.js host environment, maintaining isolation between the agent runtime and the target page.