OmniRoute Skills System Architecture: Registry, Executor, Sandbox, and Interception
OmniRoute implements a modular Skills framework that centralizes reusable functions through a singleton registry, executes them with configurable timeouts and retries, isolates risky operations in containerized sandboxes, and hooks cross-cutting concerns via an interception system.
The OmniRoute open-source routing platform provides a sophisticated skills system that lets developers register, version, and execute reusable capabilities from within the request pipeline. This architecture decouples skill definitions from their runtime environments, enabling everything from lightweight JavaScript handlers to fully sandboxed container workloads. The implementation spans four core TypeScript modules that handle metadata persistence, execution orchestration, process isolation, and cross-cutting interception.
Skill Registry: Centralized Metadata and Version Management
The Skill Registry serves as the single source of truth for skill metadata, including names, versions, JSON schemas, handler references, and enabled states. Implemented as a singleton class SkillRegistry in src/lib/skills/registry.ts, the component persists data to SQLite via getDbInstance() while maintaining two in-memory caches for high-performance lookups.
Registry Caching and Persistence
The registry maintains two cache structures: registeredSkills (a full map of all skills) and versionCache (a per-name version map). Cache invalidation occurs automatically after any database write operation, with loadFromDatabase() refreshing the cache from SQLite on demand. This dual-layer approach ensures that version-resolution logic remains fast while keeping data durable.
Version Resolution and CRUD Operations
The registry exposes explicit version-management methods: resolveVersion selects the appropriate skill version based on semantic constraints, while satisfies and compareVersions validate compatibility. CRUD operations include register for new skills, unregister and unregisterById for removal, and setEnabledById for toggling availability without deleting metadata.
Skill Executor: Controlled Runtime and Audit Trail
The Skill Executor orchestrates handler invocation and maintains a complete audit trail of every execution. Defined as a singleton SkillExecutor in src/lib/skills/executor.ts, the component looks up skills via the registry, verifies their enabled status, and inserts a tracking row into the skill_executions table before running the handler.
Execution Configuration and Lifecycle
Each execution supports configurable timeout and maxRetries settings. When a skill runs, the executor captures the output, status code, error details, and duration, persisting these to the database. Query helpers such as getExecution, listExecutions, and countExecutions enable operational monitoring and debugging.
Sandbox Runner: Container-Based Isolation
For skills requiring OS-level isolation, the Sandbox Runner provides a containerized execution environment. Located in src/lib/skills/sandbox.ts, the SandboxRunner singleton detects the available container provider through resolveProvider and caches this provider for subsequent runs.
Resource Limits and Process Management
The runner executes container images with configurable limits on CPU, memory, timeout, and network access. It tracks running containers and exposes methods to kill a single sandbox or terminate all sandboxes. Results return as a SandboxResult object containing stdout, stderr, exitCode, duration, and a kill flag indicating whether the process was forcibly terminated.
Interception System: Cross-Cutting Hooks
The Interception layer, defined in src/lib/skills/interception.ts, allows external modules to hook into the skill lifecycle without modifying skill code itself. Interceptors register to run before and after the handler invocation, enabling concerns like logging, input validation, policy enforcement, and context injection. The executor invokes these hooks automatically around each handler call.
Supporting Infrastructure: Types, Schemas, and Built-Ins
Underlying the four core components, src/lib/skills/types.ts defines TypeScript interfaces for Skill, SkillHandler, and SkillExecution, while src/lib/skills/schemas.ts provides Zod validation schemas for skill creation payloads. The system also ships with ready-made capabilities in src/lib/skills/builtins.ts and sub-folders, including browser automation tools, A2A integration, and GitHub data collectors.
Implementation Examples
Registering a Skill and Handler
To make a skill available, register its metadata in the registry and bind the implementation to the executor:
import { skillRegistry } from "@/lib/skills/registry";
import { skillExecutor } from "@/lib/skills/executor";
// Register metadata
await skillRegistry.register({
name: "echo",
version: "1.0.0",
description: "Returns the same payload back",
schema: { input: { type: "object", properties: { msg: { type: "string" } } } },
handler: "echoHandler",
enabled: true,
apiKeyId: "public",
});
// Register the actual handler function
skillExecutor.registerHandler("echoHandler", async (input, ctx) => {
return { echoed: input.msg };
});
Executing a Skill
Call the skill by name and version from anywhere in the application:
import { skillExecutor } from "@/lib/skills/executor";
const result = await skillExecutor.execute(
"echo@1.0.0",
{ msg: "hello world" },
{ apiKeyId: "user-123", sessionId: "sess-abc" }
);
console.log(result.output?.echoed); // → "hello world"
Running a Sandboxed Skill
For external binaries or unsafe scripts, use the sandbox runner:
import { sandboxRunner } from "@/lib/skills/sandbox";
const sandboxResult = await sandboxRunner.run(
"myorg/custom-tool:latest",
["node", "/app/run.js"],
{ INPUT_DATA: JSON.stringify({ foo: "bar" }) },
{ timeout: 15000 }
);
if (sandboxResult.exitCode === 0) {
console.log("stdout:", sandboxResult.stdout);
} else {
console.error("error:", sandboxResult.stderr);
}
Summary
- Skill Registry (
src/lib/skills/registry.ts): Singleton managing SQLite-backed metadata with dual-memory caching and semantic version resolution. - Skill Executor (
src/lib/skills/executor.ts): Singleton that runs handlers with timeout/retry logic and persists execution records to theskill_executionstable. - Sandbox Runner (
src/lib/skills/sandbox.ts): Container-based isolation viaSandboxRunner, enforcing resource limits and returning detailedSandboxResultobjects. - Interception (
src/lib/skills/interception.ts): Hook system for pre- and post-execution logic, enabling cross-cutting concerns without skill code changes. - Built-ins and Validation: Zod schemas for input validation and pre-built skill bundles for common integrations like browsers and GitHub.
Frequently Asked Questions
How does OmniRoute resolve which skill version to execute?
The Skill Registry stores multiple versions per skill name and exposes resolveVersion to select the appropriate implementation based on semantic constraints. The versionCache map ensures these lookups remain in-memory and fast, while the satisfies and compareVersions methods handle compatibility checks against version specifiers like "^1.0.0".
What happens if a skill execution exceeds its timeout?
The Skill Executor supports a configurable timeout parameter that limits how long a handler may run. If the timeout triggers, the execution terminates and the system records the failure status, error details, and partial duration in the skill_executions table, making it visible via listExecutions queries.
Can skills execute arbitrary system commands safely?
Yes, through the Sandbox Runner in src/lib/skills/sandbox.ts. Rather than exposing the host OS directly, the runner isolates commands inside containers with enforced CPU, memory, and network limits. The SandboxResult return value captures exit codes and stderr, allowing the caller to handle failures without risking host system integrity.
Where are skill execution logs stored?
Execution metadata—including output, status, error messages, and duration—persists to the SQLite database via the Skill Executor. The skill_executions table serves as the permanent audit trail, accessible programmatically through getExecution for single records or listExecutions for filtered queries.
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 →