# How Site Skills (Learnings) Work in ego-lite: Discovery, Loading, and Validation

> Discover how site skills work in ego-lite, from loading to validation. Understand how these reusable knowledge packs empower agents with contextual data and executable tools.

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

---

**Site skills in ego-lite are reusable, per-site knowledge packs stored under `skills/ego-browser/learnings/` that provide agents with contextual notes, selectors, and executable tools—all validated through a strict schema enforcement system before runtime use.**

In **ego-lite**, site-specific intelligence isn't hardcoded. Instead, the framework uses a modular **learning subsystem** that lets you package knowledge for any website into version-controlled directories. This article explains how these learnings are structured, discovered, loaded into the agent runtime, and rigorously validated to ensure reliability.

---

## What Site Skills (Learnings) Actually Are

A **site learning** is a directory named after its **site ID** (e.g., `google`, `x-com`) containing four core components:

| Component | Location | Purpose |
|-----------|----------|---------|
| [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) | `learnings/<site>/manifest.json` | Declares site ID, human-readable name, domain patterns, note files, and tool definitions |
| Knowledge notes | `learnings/<site>/notes/*.md` | Markdown documentation with site descriptions and selector hints |
| Node tools | `learnings/<site>/tools/*.js` | Server-side JS modules with callable functions |
| Browser tools | `learnings/<site>/browser-tools/*.js` | Client-side JS snippets executed via `site.runBrowserTool` |

The runtime discovers and loads these learnings dynamically through the learning subsystem in `package/ego-browser/src/learning/`.

---

## How Site Skills Are Discovered and Loaded

The loading pipeline involves three distinct phases: **domain matching**, **context assembly**, and **tool execution**.

### Domain Matching

When an agent requests knowledge for a URL, `siteSkillsForUrl()` in [`check-domain-learning.ts`](https://github.com/citrolabs/ego-lite/blob/main/check-domain-learning.ts) extracts the hostname and iterates over every learning directory via `iterLearningDirs`. It keeps only those learnings whose `manifest.domains` field matches the host—supporting both exact matches and wildcards like `*.example.com`.

```javascript
// Discover learnings for a URL
const { siteSkillsForUrl } = await import('ego-browser');
const matches = await siteSkillsForUrl('https://news.x.com/articles/123');
console.log(matches.map(m => m.id)); // → ["x-com"]

```

The `domainMatches` function handles pattern evaluation, comparing the extracted `urlHostname` against each declared domain pattern.

### Context Assembly

`loadLearnedContext` (exported from [`index.ts`](https://github.com/citrolabs/ego-lite/blob/main/index.ts)) orchestrates the full loading process. It calls `siteSkillsForUrl()` to gather matching learnings, then reads each note file and builds **tool signatures** for both node and browser tools. The result is a `LearnedContext` object consumed through helpers like `site.runTool()` and `site.runBrowserTool()`.

```javascript
// Load the full learned context (notes + tool signatures)
import { loadLearnedContext } from 'ego-browser';
const ctx = await loadLearnedContext('https://google.com');
if (ctx.exists) {
  console.log('Site name:', ctx.siteName);
  console.log('Available tools:', ctx.tools.map(t => t.toolName));
  console.log('Notes files:', ctx.knowledge.map(n => n.fileName));
}

```

### Tool Execution

- **Node tools**: Dynamically imported via `import()` through `runNodeSiteTool` in [`index.ts`](https://github.com/citrolabs/ego-lite/blob/main/index.ts), then invoked with the exported callable function
- **Browser tools**: Read as plain text via `loadBrowserToolSource()`, wrapped in an async IIFE via `wrapBrowserTool()`, and executed via `site.runBrowserTool()`

```javascript
// Running a node-tool from a learning
await site.runTool('google', 'search', { query: 'open source AI' });
// Loads skills/ego-browser/learnings/google/tools/search.js and invokes the exported callable

```

All learning helpers are exposed to agents through `helperContext()` in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts).

---

## The Site Skills Validation Process

Before any learning can be used, it must pass **comprehensive schema validation** implemented in [`validate-learning-format.ts`](https://github.com/citrolabs/ego-lite/blob/main/validate-learning-format.ts). The `validateLearning()` and `validateLearnings()` functions enforce a strict contract across six validation layers.

### Manifest Validation

The validator confirms:
- [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) exists and parses as valid JSON
- `id` matches the directory name exactly
- `name` is non-empty
- `domains` is a non-empty array of well-formed patterns

### Domain Pattern Checks

Each domain must be either:
- A plain hostname (`example.com`)
- A single leading wildcard (`*.example.com`)

The `isValidDomain()` function rejects malformed patterns.

### Note File Verification

Every entry in `manifest.notes` must:
- Use relative paths matching `notes/*.md`
- Point to existing files (verified via `requireFile`)
- **Exclude temporary snapshot references** (blocked by `rejectTemporaryRefs`)

This prevents agents from relying on fragile, auto-generated selectors that break on page changes.

### Tool Schema Enforcement

| Tool Type | Requirements |
|-----------|--------------|
| **Node tools** | Relative `tools/*.js` path, non-empty `callable` name, valid `args`/`returns` schema; JS file is imported to verify callable exists |
| **Browser tools** | Relative `browser-tools/*.js` path, valid argument/return schemas; source checked for temporary refs |

### Value Type Constraints

Argument and return schemas are restricted to JSON-Schema primitives defined in `TOOL_VALUE_TYPES`. This ensures type-safe serialization between the agent runtime and tool implementations.

### Safety Sanitization

Tool names and paths undergo `isSafeToolName()` and `isSafeRelativePath()` checks to prevent:
- Directory traversal attacks
- Malformed identifiers
- Path injection vulnerabilities

### Running Validation

The top-level entry point `validateLearnings()` (exported as `validateSiteSkills`) walks all learning directories via `iterLearningDirs()` and aggregates errors. The repository includes a CLI convenience script:

```bash

# Validate all site skills in the repository

node package/ego-browser/scripts/validate-site-skills.ts

# Exits code 0 when all learnings pass; prints detailed errors otherwise

```

---

## Why Site Skill Validation Matters

- **Selector stability** — Rejecting temporary refs (`@123` or `ref=123`) forces durable, maintainable locators
- **Runtime reliability** — Catches missing files and malformed manifests before deployment
- **Deterministic contracts** — Tool argument/return schemas enable automatic documentation generation via `site.runTool().help()`

---

## Summary

- Site skills in ego-lite are modular knowledge packs under `skills/ego-browser/learnings/<site-id>/`
- Discovery uses hostname matching against `manifest.domains` patterns in [`check-domain-learning.ts`](https://github.com/citrolabs/ego-lite/blob/main/check-domain-learning.ts)
- Full context loading combines notes and tool signatures via `loadLearnedContext()` in [`index.ts`](https://github.com/citrolabs/ego-lite/blob/main/index.ts)
- Node tools execute server-side via dynamic `import()`; browser tools inject into page context
- Six-layer validation in [`validate-learning-format.ts`](https://github.com/citrolabs/ego-lite/blob/main/validate-learning-format.ts) enforces manifest integrity, domain correctness, file existence, schema compliance, type safety, and path sanitization
- Use `validateLearnings()` or [`scripts/validate-site-skills.ts`](https://github.com/citrolabs/ego-lite/blob/main/scripts/validate-site-skills.ts) to verify learnings before runtime use

---

## Frequently Asked Questions

### How do I create a new site skill for ego-lite?

Create a directory under `skills/ego-browser/learnings/` named with your site ID (e.g., `my-site`). Add a [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) declaring `id`, `name`, and `domains`. Place Markdown notes in `notes/` and JavaScript tools in `tools/` (server-side) or `browser-tools/` (client-side). Run the validation script to verify your learning passes all checks before committing.

### What happens if a learning fails validation?

`validateLearnings()` collects all errors and returns them in detail—manifest syntax failures, missing files, invalid domain patterns, schema mismatches, or temporary snapshot references. The CLI script exits with a non-zero status code, blocking CI/CD pipelines from deploying broken learnings.

### Can site skills use wildcards in domain patterns?

Yes. The `domainMatches` function supports `*.example.com` patterns for subdomain matching. However, wildcards are restricted to a single leading asterisk—patterns like `example.*.com` are rejected by `isValidDomain()` to prevent ambiguous matching.

### Where does ego-lite load site skills from at runtime?

The `iterLearningDirs()` function scans `skills/ego-browser/learnings/` relative to the working directory. This discovery happens dynamically in `siteSkillsForUrl()` and `loadLearnedContext()`, allowing hot-reloading of learnings without restarting the agent runtime.