How to Implement Skill-Based Agent Workflows and Orchestrators in Routa
Routa implements skill-based agent workflows through a three-layer architecture comprising skill discovery, registry management, and runtime resolution, enabling agents to dynamically load markdown-based prompt bundles at execution time.
Routa, developed by phodal, treats skills as reusable, markdown-based prompt bundles that agents can load and execute at runtime. This architecture decouples prompt engineering from agent logic, allowing teams to version, share, and permission prompt templates independently of orchestration code. Understanding how to implement skill-based agent workflows requires familiarity with three core layers: discovery and loading, registry and permissioning, and runtime resolution.
The Three-Layer Skill Architecture
Discovery and Loading
In src/core/skills/skill-loader.ts, the system walks well-known directories including project-local (./.agents/skills/), global (~/.config/opencode/skills/), and repository-local paths. It parses each SKILL.md file into a SkillDefinition object, validating front-matter such as name, description, and optional metadata while capturing the markdown body as content.
Registry and Permissioning
The src/core/skills/skill-registry.ts maintains an in-memory Map of discovered skills. It enforces a permission model using allow | deny | ask states that determine UI visibility and execution rights. The registry exposes getSkill for direct lookups and listSkillSummaries for cheap frontend enumeration.
Resolution at Call-Time
src/core/skills/skill-resolver.ts serves as the orchestrator that ACP (AI-Code-Prompt) sessions invoke when requests contain a skillName. The resolver checks the registry first, falls back to repository-local discovery, and finally queries persisted stores (Postgres or SQLite) if the skill resides in the backend. The first non-empty content found is returned to the ACP runtime.
Defining Your First Skill
Create a SKILL.md file with YAML front-matter:
---
name: my-skill
description: Generate a React component skeleton from a spec.
short-description: React component generator
license: MIT
---
## Prompt
You are a front‑end engineer. Generate a functional React component in TypeScript that matches the following spec:
{{spec}}
Place this under ./.agents/skills/my-skill/SKILL.md for project-local discovery or ~/.config/opencode/skills/my-skill/SKILL.md for global access. The loader automatically picks it up on the next scan.
Orchestrating Skills in Agent Sessions
To programmatically resolve a skill within a custom orchestrator, use resolveSkillContent from src/core/skills/skill-resolver.ts:
import { resolveSkillContent } from '@/core/skills/skill-resolver';
async function runSkillInSession(sessionId: string, spec: string) {
// Resolve the markdown for the skill named "my-skill"
const skillMarkdown = await resolveSkillContent('my-skill');
if (!skillMarkdown) throw new Error('Skill not found');
// Build an ACP payload that includes the resolved skill
const acpPayload = {
sessionId,
prompt: spec,
skillContent: skillMarkdown, // directly inject the markdown
};
// Send to the AI back‑end (e.g., Claude or OpenAI)
const response = await fetch('/api/acp', {
method: 'POST',
body: JSON.stringify(acpPayload),
headers: { 'Content-Type': 'application/json' },
});
return response.json();
}
If skillContent is omitted from the payload, the server automatically invokes resolveSkillContent during request processing, as implemented at the end of skill-resolver.ts.
Extending the Skill System
Each architectural layer offers distinct extension points:
| Layer | Extensibility | Implementation Location |
|---|---|---|
| Discovery | Add new directories or file extensions | src/core/skills/skill-loader.ts via getProjectSkillDirs, getGlobalSkillDirs, or getRepoSkillDirs |
| Registry | Custom permission rules or augmented metadata | src/core/skills/skill-registry.ts |
| Resolution | New storage backends (e.g., MongoDB) | src/core/skills/skill-resolver.ts after Postgres/SQLite checks |
| UI | Custom run buttons or catalog integrations | src/client/components/skill-panel.tsx |
| API | Bulk import, versioning, or remote execution | src/client/skill-client.ts and src/pages/api/skills/* |
To add MongoDB support, for example, modify skill-resolver.ts:
// src/core/skills/skill-resolver.ts (excerpt)
// 4. MongoDB (new)
try {
const { getDatabaseDriver } = require('../db/index');
if (getDatabaseDriver() === 'mongodb') {
const { MongoSkillStore } = require('../db/mongo-skill-store');
const store = new MongoSkillStore();
const stored = await store.get(skillName);
if (stored?.content) return stored.content;
}
} catch {
// MongoDB load failed – continue
}
Then implement mongo-skill-store.ts with get(id) and toSkillDefinition methods.
Integrating the Skill Panel UI
The frontend exposes skills through src/client/components/skill-panel.tsx, which consumes the useSkills hook:
import { SkillPanel } from '@/client/components/skill-panel';
import { useSkills } from '@/client/hooks/use-skills';
export default function Sidebar() {
const skillsHook = useSkills(); // shared hook for chat & sidebar
return <SkillPanel skillsHook={skillsHook} />;
}
This component renders a browsable sidebar supporting installation, cloning from GitHub, and uploading ZIP archives. Selections trigger loadSkill(name), which fetches /api/skills?name=${name} and displays the markdown in a viewer.
Key Implementation Files
| File | Purpose |
|---|---|
src/core/skills/skill-loader.ts |
Discovers SKILL.md files, parses front-matter, returns SkillDefinition |
src/core/skills/skill-registry.ts |
In-memory registry, permission handling, skill summary API |
src/core/skills/skill-resolver.ts |
Orchestrates resolution from registry → repo → DB (Postgres/SQLite) |
src/client/components/skill-panel.tsx |
Interactive sidebar for browsing, installing, and loading skills |
src/client/skill-client.ts |
Thin wrapper around /api/skills REST endpoints |
Summary
- Routa's skill system comprises three layers: discovery (
skill-loader.ts), registry (skill-registry.ts), and resolution (skill-resolver.ts). - Skills are markdown files with YAML front-matter stored in predictable directory structures.
- The
resolveSkillContentfunction serves as the primary orchestration entry point, checking registry, filesystem, and database sources in sequence. - Extensions require modifying specific files: add directories in the loader, permissions in the registry, storage backends in the resolver, and UI features in the skill panel.
- The frontend
SkillPanelcomponent provides out-of-the-box skill management integrated with the orchestration backend.
Frequently Asked Questions
How does Routa resolve a skill when multiple storage backends are configured?
Routa's skill-resolver.ts implements a cascading resolution strategy. It first checks the in-memory registry, then scans repository-local directories, and finally queries persisted databases (Postgres or SQLite). The resolver returns the first non-empty content found, stopping immediately upon discovery.
Can I implement custom permission rules for skill execution?
Yes. The src/core/skills/skill-registry.ts file manages a permission model supporting allow, deny, and ask states. You can extend the registry logic to implement role-based access control, team-based permissions, or environment-gated execution by modifying how the registry evaluates these states before returning skill definitions.
What is the difference between skill loading and skill resolution?
Loading (skill-loader.ts) occurs during discovery phases, parsing SKILL.md files into SkillDefinition objects. Resolution (skill-resolver.ts) happens at call-time when an agent session requests a specific skill by name, retrieving the content from the most appropriate available source (memory, disk, or database).
How do I add a new skill storage backend like MongoDB?
Extend src/core/skills/skill-resolver.ts by adding a new resolution branch after the existing Postgres/SQLite checks. Import your custom store (e.g., MongoSkillStore), verify the database driver configuration, and attempt to retrieve the skill content. If successful, return immediately; otherwise, continue to the next fallback source.
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 →