# How Ego-Lite Discovers and Executes Site Skills (Learnings)

> Learn how Ego-Lite discovers and executes site skills by scanning for domain-matching manifests, dynamically loading notes and tool signatures for import or evaluation.

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

---

**Ego-Lite discovers and executes site skills by scanning `skills/ego-browser/learnings/<site>/` for manifests matching a URL's domain, then dynamically loads notes and tool signatures into a `LearnedContext` for Node-side import or browser-side evaluation.**

Site skills—called **"learnings"** in the ego-lite codebase—are reusable, site-specific automation packs that agents can invoke without hard-coded logic. This article explains the complete discovery and execution pipeline implemented in the `ego-browser` package, from domain matching to runtime tool invocation.

---

## What Are Site Skills (Learnings)?

A **learning pack** is a directory under `skills/ego-browser/learnings/<site-id>/` containing:

- [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) — domain patterns, tool definitions, and note references
- `notes/*.md` — Markdown documentation describing site behavior
- `tools/` — Node-side JavaScript/TypeScript modules
- `browser-tools/` — raw JavaScript for in-page execution

The runtime treats these as **pluggable expertise** that agents retrieve on demand based on the current URL.

---

## Step 1: Locating the Correct Learning Pack

The discovery pipeline starts with **`siteSkillsForUrl`** in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts). This helper calls **`siteSkillsForUrlCore`** (defined in [`src/learning/check-domain-learning.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/check-domain-learning.ts)) to scan the **learnings directory** for matching manifests.

```ts
// helpers.ts – locate site skills for a URL
export async function siteSkillsForUrl(url) {
  return siteSkillsForUrlCore(url, { agentWorkspace: state.agentWorkspace() });
}

```

The **learnings root** defaults to `skills/ego-browser/learnings` relative to the agent workspace, as implemented in [`src/learning/check-domain-learning.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/check-domain-learning.ts). The core function iterates site directories, parses each [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json), and returns **`LearningEntry`** objects whose `domains` array matches the target URL's hostname.

Each `LearningEntry` contains:
- `id` and `name` — site identifiers
- `domains` — matching hostname patterns
- Paths to notes and declared tools

---

## Step 2: Loading the Learned Context

Once matches are found, **`learnContext(url?)`** (in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)) forwards to **`loadLearnedContext`** in [`src/learning/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/index.ts) to build a structured knowledge object.

```ts
// learning/index.ts – create the learned context
export async function loadLearnedContext(url: string, options = {}): Promise<LearnedContext> {
  const matches = await siteSkillsForUrl(url, options);
  // ... reads notes, builds tool signatures
  return {
    exists: true,
    siteId: matches[0].id,
    siteName: matches[0].name,
    domain: urlHostname(url),
    knowledge: knowledgeNotes,  // content of notes/*.md
    tools: toolSignatures,      // from manifest.nodeTools + manifest.browserTools
  };
}

```

The resulting **`LearnedContext`** provides:
- **Boolean `exists`** — whether a matching pack was found
- **`siteId`/`siteName`** — identifiers for the matched site
- **`domain`** — the extracted hostname
- **`knowledge`** — concatenated Markdown notes
- **`tools`** — complete signatures with descriptions, argument schemas, return schemas, and usage examples

Tool signatures are derived from both `nodeTools` and `browserTools` sections in the manifest, enabling the agent to understand available capabilities before invocation.

---

## Step 3: Running Node-Side Tools

For server-side execution, **`runSiteTool(siteId, toolName, args?)`** in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) invokes **`runNodeSiteTool`**:

```ts
// helpers.ts – execute a Node tool
export async function runSiteTool(siteId, toolName, args = {}) {
  return runNodeSiteTool(siteId, toolName, args, helperContext(), {
    agentWorkspace: state.agentWorkspace(),
  });
}

```

The execution flow:

1. **`findSiteSkill`** retrieves the manifest by `siteId`
2. Validates that `toolName` exists in `manifest.nodeTools`
3. Resolves the relative path to the tool module
4. Dynamically imports via `import(pathToFileURL(toolPath).href...)`
5. Calls the exported function named in `manifest.nodeTools[toolName].callable` with helper context and supplied arguments

This **dynamic import pattern** allows learning packs to ship arbitrary Node.js logic without recompiling the core framework.

---

## Step 4: Running Browser-Side Tools

For DOM manipulation within the active page, **`runSiteBrowserTool(siteId, toolName, args?)`** provides a different execution model:

```ts
// helpers.ts – execute a browser tool
export async function runSiteBrowserTool(siteId, toolName, args = {}) {
  const source = await loadBrowserToolSource(siteId, toolName, {
    agentWorkspace: state.agentWorkspace(),
  });
  return evaluate(wrapBrowserTool(source, args));
}

```

The pipeline:
1. **`loadBrowserToolSource`** reads raw JavaScript from the learning pack's `browser-tools/` directory
2. **`wrapBrowserTool`** injects the source into an async IIFE with argument binding
3. **`evaluate`** executes the wrapped code in the current browser context

This **source-wrapping approach** avoids module bundling constraints and lets learning packs inject arbitrary page scripts dynamically.

---

## Validation and Schema Enforcement

The **`validateLearning`**, **`validateLearnings`**, and **`validateSiteSkills`** utilities in [`src/learning/validate-learning-format.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/validate-learning-format.ts) ensure manifest integrity. These power the CLI command:

```bash
npm run validate:site-skills

```

Validation checks:
- Required fields: `domains`, `id`, `name`
- Tool definitions: `callable` references, path existence
- Note locations: valid `notes/*.md` references
- Schema compliance for arguments and returns

---

## Complete Execution Flow

```

URL → siteSkillsForUrlCore → LearningEntry[] → loadLearnedContext
   ├─► learnContext()        → LearnedContext (knowledge + tool signatures)
   ├─► runSiteTool()         → dynamic import → Node tool execution
   └─► runSiteBrowserTool()  → load source → wrap → evaluate in page

```

---

## Key Source Files

| File | Purpose |
|------|---------|
| [`src/learning/index.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/index.ts) | Core discovery, note loading, tool signature building, Node tool execution |
| [`src/learning/check-domain-learning.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/check-domain-learning.ts) | Learnings directory location, site iteration, domain matching |
| [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) | Public API: `siteSkillsForUrl`, `learnContext`, `runSiteTool`, `runSiteBrowserTool` |
| [`src/learning/validate-learning-format.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/validate-learning-format.ts) | Manifest schema validation |

---

## Summary

- **Discovery**: `siteSkillsForUrlCore` scans `skills/ego-browser/learnings/` for manifests matching the target URL's domain
- **Loading**: `loadLearnedContext` assembles Markdown notes and tool signatures into a `LearnedContext`
- **Node execution**: `runSiteTool` dynamically imports and calls modules specified in `manifest.nodeTools`
- **Browser execution**: `runSiteBrowserTool` fetches, wraps, and evaluates raw JavaScript in the active page
- **Validation**: Schema checks via [`validate-learning-format.ts`](https://github.com/citrolabs/ego-lite/blob/main/validate-learning-format.ts) ensure pack integrity

---

## Frequently Asked Questions

### What directory structure is required for a site skill?

A learning pack requires `skills/ego-browser/learnings/<site-id>/` containing [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) with `domains`, `nodeTools`, and/or `browserTools` arrays, plus referenced `notes/*.md` and tool directories. The [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) is the single source of truth for discovery and execution.

### How does ego-lite match a URL to the correct learning pack?

The `siteSkillsForUrlCore` function in [`src/learning/check-domain-learning.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/check-domain-learning.ts) compares the URL's hostname against each manifest's `domains` array. First match wins; the function returns a `LearningEntry` with paths and tool metadata for that site.

### Can a learning pack contain both Node and browser tools?

Yes. The [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) supports both `nodeTools` (dynamically imported server-side modules) and `browserTools` (raw JavaScript evaluated in-page). The `LearnedContext` aggregates signatures from both sections for agent consumption.

### How are tool arguments validated at runtime?

Argument schemas are declared in the manifest for each tool. While the core helpers don't enforce runtime validation automatically, the `validate:site-skills` CLI command ensures schemas are present and well-formed. Tool implementations handle their own argument parsing and error handling.