How the Skills Framework in OmniRoute Works: Registry, Executor, Sandbox, and Built-in Catalog
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) 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) 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) 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) 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 walks the skills/ folder, reads each 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
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 lines 13–14) and persisted in the skills table.
List Available Skills
GET https://localhost:20128/api/skills
Authorization: Bearer <api-key>
The route calls skillRegistry.scopedSkills(apiKeyId) (lines 54–73 in registry.ts), merging personal and global skills.
Execute a Skill
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:
// 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:
// 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) is a singleton that caches metadata in memory for 60 seconds and merges global skills with user-scoped entries viascopedSkills(). - The Sandbox Runner (
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 structuredSandboxResult. - The Skill Executor (
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) auto-imports everySKILL.mdin theskills/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), 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 walks the skills/ directory, reads each 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →