# How the Ego-Lite Learning Subsystem Discovers and Validates Site Skills

> Learn how the ego lite learning subsystem discovers site skills by scanning manifest files and validates pack structure and tool schemas. See how it integrates with browser automation helpers.

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

---

**The learning subsystem in citrolabs/ego-lite discovers site skills by scanning the agent workspace for manifest files matching URL domains, then validates pack structure and tool schemas before exposing them to browser automation helpers.**

The learning subsystem resides in `package/ego-browser/src/learning` and serves as the intelligence layer that maps URLs to specialized automation capabilities. It performs a two-phase pipeline: first discovering which site-skill packs apply to a given domain, then rigorously validating those packs to prevent runtime errors. This article examines the complete discovery and validation flow using the actual implementation from the citrolabs/ego-lite repository.

## Discovery Phase: Matching URLs to Site-Skill Packs

The discovery process begins when the system needs to find applicable automation skills for a specific URL. The implementation in [`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) orchestrates this through several sequential steps.

### Locating the Learning Root and Enumerating Candidates

The `learningsRoot()` function establishes the base directory at `<agent-workspace>/learnings`, which serves as the central repository for all site-skill packs. The `iterLearningDirs(root)` function then reads this directory, filters out hidden folders (those starting with dots), and returns a sorted list of sub-folders where each folder represents a distinct site-skill pack.

```typescript
// From check-domain-learning.ts
const root = learningsRoot();  // Returns <agent-workspace>/learnings
const candidates = iterLearningDirs(root);  // Sorted list of site directories

```

### Domain Matching and Manifest Loading

For each candidate directory, `loadLearningManifest(siteDir)` parses the [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) file. If parsing fails, the directory is skipped silently. The `siteSkillsForUrl(url)` function extracts the hostname from the provided URL and compares it against each manifest's `domains` array using `domainMatches(hostname, pattern)`, which supports wildcard patterns such as `*.example.com`.

When a match occurs, `learningEntry(siteDir, manifest)` constructs a `LearningEntry` object containing the pack ID, name, filesystem path, applicable domains, note file paths, and tool schemas for both Node-side and browser-side execution.

```typescript
// Example from the public API
import { site } from "ego-browser";

const ctx = await site.learnContext("https://www.yoursite.com/dashboard");
// Returns LearningEntry with tools, notes, and metadata

```

## Validation Phase: Ensuring Pack Integrity

Once discovered, site-skill packs undergo strict validation via [`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). This prevents malformed packs from causing runtime failures during automation tasks.

### Manifest Structure Verification

The `validateLearning(siteDir)` function performs the initial schema checks. It verifies that the `id` field matches the directory name, ensures `name` is a non-empty string, and confirms `domains` is a non-empty array of syntactically valid domains using `isValidDomain()`. These checks occur in lines 28-44 of the validation module.

```typescript
// Validation rules enforced:
// - id === directoryName
// - name.length > 0
// - domains.every(isValidDomain)
// - domains.length > 0

```

### Tool Schema and File Validation

For **Node-side tools** listed in `nodeTools`, the validator checks that each tool's `path` points to `tools/*.js`, confirms file existence via `pathExists`, and verifies the module exports the callable name defined in `schema.callable` using `validateToolSchema`. It also scans for temporary snapshot references (patterns like `@123` or `ref=123`) via `rejectTemporaryRefs` and rejects any files containing them.

For **Browser-side tools** in `browserTools`, similar checks apply except the path must be `browser-tools/*.js`. While these tools execute in the browser context and aren't dynamically imported during validation, the system still verifies file existence and rejects temporary references.

```typescript
// Node tool validation (lines 54-73):
// 1. validateToolSchema(schema)
// 2. path matches tools/*.js pattern
// 3. File exists at path
// 4. Module exports schema.callable function
// 5. No temporary refs in file content

// Browser tool validation (lines 75-82):
// Similar checks for browser-tools/*.js paths

```

The `validateLearnings(root)` function orchestrates this process by iterating over every learning directory and aggregating all errors into a single report. This powers the CLI command `npm run validate:site-skills`, which outputs validation failures for immediate developer feedback.

## Integration: Using Discovered and Validated Skills

The public API exposed in [`package/ego-browser/src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) combines both phases seamlessly. The `site.learnContext(url)` method calls `siteSkillsForUrl` for discovery and returns a `LearnedContext` object containing validated tool signatures ready for execution.

```typescript
// Accessing discovered tools
const context = await site.learnContext("https://news.ycombinator.com");
console.log(context.tools.map(t => t.toolName)); // ["fetchPosts", "comment"]

// Executing Node-side validated tools
const result = await site.runTool(
  "hackernews",
  "fetchPosts",
  { limit: 10 }
);

// Executing Browser-side validated tools
await site.runBrowserTool("hackernews", "highlightPost");

```

## Summary

- **Discovery** starts at `<agent-workspace>/learnings` and filters directories by matching URL hostnames against manifest domain patterns using `domainMatches()`.
- **Validation** enforces strict schema compliance in [`validate-learning-format.ts`](https://github.com/citrolabs/ego-lite/blob/main/validate-learning-format.ts), checking manifest integrity, file existence, tool callability, and prohibiting temporary snapshot references.
- **Integration** occurs through `site.learnContext()`, which returns validated `LearningEntry` objects containing ready-to-use tool schemas.
- **CLI validation** via `npm run validate:site-skills` runs `validateLearnings()` to catch errors before runtime deployment.
- The subsystem distinguishes between **Node-side tools** (validated for module exports) and **Browser-side tools** (validated for presence only).

## Frequently Asked Questions

### How does the learning subsystem handle wildcard domains?

The `domainMatches(hostname, pattern)` function in [`check-domain-learning.ts`](https://github.com/citrolabs/ego-lite/blob/main/check-domain-learning.ts) supports wildcard syntax such as `*.example.com`. When iterating through a manifest's `domains` array, it matches the extracted URL hostname against these patterns, allowing a single site-skill pack to apply to all subdomains of a target site.

### What happens if a site-skill manifest contains temporary snapshot references?

During validation, `rejectTemporaryRefs()` scans note files and tool source code for patterns like `@123` or `ref=123`. If found, the validation fails with an error indicating the file contains temporary references. This prevents unstable snapshot IDs from reaching production automation scripts.

### Can I validate site-skill packs without running the full browser automation?

Yes. Run the CLI command `npm run validate:site-skills` to execute `validateLearnings()` directly. This performs static analysis of all manifests, tool schemas, and file references without launching browser instances or executing tool code, making it suitable for CI/CD pipelines.

### What is the difference between Node-side and Browser-side tool validation?

Node-side tools undergo full validation: the system checks that the JavaScript file exists, dynamically verifies the module exports the `callable` function defined in the schema, and validates the schema shape using `validateToolSchema`. Browser-side tools only validate file existence at `browser-tools/*.js` paths and check for temporary references, since they execute in the browser context and cannot be statically imported during validation.