# How the Skills Framework in OmniRoute Works: Registry, Executor, Sandbox, and Built-in Catalog

> Discover how OmniRoute's Skills Framework works with its registry, executor, sandbox, and catalog to run custom skills in isolated containers.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: internals
- Published: 2026-08-22

---

**The Skills Framework in OmniRoute is a plugin system that lets developers register, discover, and run custom skills inside isolated containers through a singleton registry, a sandbox runner, and a built-in catalog loader.**

The **Skills Framework in OmniRoute** turns the open-source router into an extensible platform where developers can deploy custom "skills"—small functions or agents—without risking host integrity. According to the diegosouzapw/OmniRoute source code, the framework coordinates four main pillars: a central registry for metadata, an executor for orchestration, a sandbox for isolation, and a built-in skills catalog that ships with the system.

## Skill Registry: Central Metadata and Caching

The **Skill Registry** ([`src/lib/skills/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/registry.ts)) stores every skill's metadata in an SQLite `skills` table and maintains hot in-memory caches for fast lookups.

### Singleton Pattern and In-Memory Caching

`SkillRegistry` is a singleton accessed via `SkillRegistry.getInstance()`. It maintains two in-memory maps—`registeredSkills` and `versionCache`—that expire after a 60-second TTL (`cacheTTL`). When a skill is looked up, the registry serves it from memory; when a write occurs, `invalidateCache()` ensures stale data is dropped immediately.

### Global vs. User-Scoped Skills

The registry distinguishes **global** skills (owned by the system) from user-scoped skills. The `scopedSkills(apiKeyId)` method (lines 54–73 in [`registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/registry.ts)) transparently merges a user's own skills with the global catalog, de-duplicating entries that share an identical `name@version` identifier.

### Registration and Validation

All writes pass through a Zod-validated `SkillCreateInputSchema` and are persisted inside an SQLite transaction using `db.prepare(...).run`. The `register()` method caches the new entry and expires the global cache so subsequent lookups see the fresh skill immediately.

## Sandbox Runner: Isolated Container Execution

The **Sandbox Runner** ([`src/lib/skills/sandbox.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/sandbox.ts)) executes a skill's handler inside an isolated container via Docker, Podman, or any compatible `ContainerProvider`.

### Default Resource Limits

`SandboxRunner` is also a singleton (`SandboxRunner.getInstance()`) that caches the resolved container provider for subsequent runs. By default, every sandbox is constrained to **100% CPU**, **256 MiB RAM**, and a **30-second timeout** defined in `DEFAULT_CONFIG`. These limits prevent a buggy or malicious skill from starving host resources.

### Sandbox Lifecycle and Cleanup

The `run(image, command, env, configOverride)` method spawns the container, captures `stdout` and `stderr`, and resolves a `SandboxResult` object containing the exit code and output. The runner supports killing a single sandbox (`kill`) or terminating all running sandboxes (`killAll`), guaranteeing cleanup on completion or failure.

## Skill Executor: Orchestrating Validation and Invocation

The **Skill Executor** ([`src/lib/skills/executor.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/executor.ts)) bridges the registry and the sandbox, ensuring that every invocation is validated before it reaches the container.

### Schema Validation Before Runtime

When a skill is invoked via `POST /api/skills/executions`, the executor retrieves the skill definition from the registry and validates incoming `args` against the stored JSON schema. If validation fails, the request is rejected before any container is spawned.

### Calling the Sandbox and Packaging Results

If the skill's `mode` is `"on"`, the executor calls `sandboxRunner.run()` with the container image and command defined in the skill's `handler`. It then collects the `SandboxResult`, sanitizes `stdout` and `stderr`, and returns a structured response to the caller.

## Built-in Skills Catalog

OmniRoute ships with a **Built-in Skills Catalog**—pre-packaged skills such as `omni-webhooks`, `omni-settings`, and `omni-tunnels` that live in the `skills/` directory.

### Auto-Registration from SKILL.md Files

During startup, [`src/lib/skills/builtins.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/builtins.ts) walks the `skills/` folder, reads each [`SKILL.md`](https://github.com/diegosouzapw/OmniRoute/blob/main/SKILL.md) file, extracts the JSON definition, and calls `skillRegistry.register(...)` for every built-in. This happens automatically before the router accepts traffic.

### Global Visibility for All Clients

Built-in skills are registered under the system owner ID (`GLOBAL_SKILL_OWNER_ID = "system"`). Because they are globally scoped, they are visible to every API key without requiring extra permissions, making them available to all clients immediately.

## Practical Code Examples

### Register a Custom Skill

```http
POST https://localhost:20128/api/skills/skillssh
Content-Type: application/json
Authorization: Bearer <api-key>

{
  "name": "my-hello",
  "version": "1.0.0",
  "description": "Returns a greeting",
  "schema": { "type": "object", "properties": { "name": { "type": "string" } }, "required": ["name"] },
  "handler": "docker://my-skill-image:latest node /app/index.js",
  "enabled": true,
  "mode": "on",
  "sourceProvider": "skillssh"
}

```

The payload is parsed with `SkillCreateInputSchema` (see [`registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/registry.ts) lines 13–14) and persisted in the `skills` table.

### List Available Skills

```http
GET https://localhost:20128/api/skills
Authorization: Bearer <api-key>

```

The route calls `skillRegistry.scopedSkills(apiKeyId)` (lines 54–73 in [`registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/registry.ts)), merging personal and global skills.

### Execute a Skill

```http
POST https://localhost:20128/api/skills/executions
Content-Type: application/json
Authorization: Bearer <api-key>

{
  "skillId": "my-hello@1.0.0",
  "args": { "name": "Alice" }
}

```

The executor resolves the skill, validates `args`, and runs it inside the sandbox:

```typescript
// Inside src/lib/skills/executor.ts (simplified)
const skill = skillRegistry.getSkillById(skillId);
const validated = skill.schema.parse(args);
const result = await sandboxRunner.run(skill.handlerImage, skill.handlerCommand, validated);
return { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode };

```

### Invoke a Built-in Skill

Built-in skills such as `omni-webhooks` are registered automatically and used exactly like user-defined skills:

```typescript
// In src/lib/skills/builtins.ts (excerpt)
await skillRegistry.register({
  name: "omni-webhooks",
  version: "0.1.0",
  description: "Manage webhook endpoints",
  schema: {/* … */},
  handler: "docker://omniroute-webhooks:latest node /app/webhook.js",
  enabled: true,
  mode: "on",
  sourceProvider: "system"
});

```

## Summary

- The **Skill Registry** ([`src/lib/skills/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/registry.ts)) is a singleton that caches metadata in memory for 60 seconds and merges global skills with user-scoped entries via `scopedSkills()`.
- The **Sandbox Runner** ([`src/lib/skills/sandbox.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/sandbox.ts)) isolates every handler in a container with default limits of 100% CPU, 256 MiB RAM, and 30 seconds, returning a structured `SandboxResult`.
- The **Skill Executor** ([`src/lib/skills/executor.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/executor.ts)) validates arguments against the stored JSON schema before invoking the sandbox and packages the final output.
- The **Built-in Skills Catalog** ([`src/lib/skills/builtins.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/builtins.ts)) auto-imports every [`SKILL.md`](https://github.com/diegosouzapw/OmniRoute/blob/main/SKILL.md) in the `skills/` directory at startup and registers them under the system owner ID.

## Frequently Asked Questions

### How does OmniRoute prevent a malicious skill from affecting the host?

OmniRoute executes every skill handler inside the **Sandbox Runner** ([`src/lib/skills/sandbox.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/sandbox.ts)), which spawns an isolated container via Docker or Podman. The sandbox enforces CPU, memory, and timeout limits and cleans up processes on completion or failure, ensuring the host process remains unaffected.

### What is the difference between global and user-scoped skills?

**Global skills** are owned by the system (`GLOBAL_SKILL_OWNER_ID = "system"`) and are visible to every API key. **User-scoped skills** belong to a specific API key and are private to that client. The registry's `scopedSkills()` method merges both lists transparently while de-duplicating identical `name@version` entries.

### How are built-in skills loaded when OmniRoute starts?

The **Built-in Skills Catalog** loader in [`src/lib/skills/builtins.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/builtins.ts) walks the `skills/` directory, reads each [`SKILL.md`](https://github.com/diegosouzapw/OmniRoute/blob/main/SKILL.md) file, extracts its JSON definition, and calls `skillRegistry.register(...)` during startup. Because they are registered as system-owned, they require no manual installation or extra permissions.

### Can sandbox resource limits be customized per skill?

Yes. While the **Sandbox Runner** defaults to 100% CPU, 256 MiB RAM, and a 30-second timeout (`DEFAULT_CONFIG`), the `run()` method accepts a `configOverride` parameter. This allows individual skill invocations to specify tighter or looser container constraints as needed.