Skip to content
Pilotoo
v0.1

Skills

A skill is a folder of instructions an agent loads when it is relevant: a SKILL.md plus optional reference files. Skills keep long, specialised guidance out of the system prompt until it is needed. Use them for procedures, house style or domain rules that only some conversations require.

skills/
my-skill/
SKILL.md # frontmatter (name, description) + Markdown
references/... # other files under the folder are references
skills/my-skill/SKILL.md
---
name: my-skill
description: "Use when: the user asks for a weekly report"
---
Steps the agent follows...

An agent selects skills by name in runtime.skills (null means every available skill). The demo API reads the repository’s skills/ folder through AGENT_SKILLS_DIR. See Agents.

A SkillSource lists skills. Combine as many as you like, for example a folder and a database.

import { createDirectorySkillSource, type SkillSource } from '@pilotoo/agent-runtime';
const stored: SkillSource = { label: 'host-db', listSkills: () => db.skills.all() };
const skillSources = [createDirectorySkillSource('/srv/skills'), stored];

With PilotooModule, the skills directory is AGENT_SKILLS_DIR and your own sources go in the skillSources option (token HOST_SKILL_SOURCES). They rank after the directory when two provide one name.

The kit guarantees:

  • A directory source reads <dir>/<name>/SKILL.md with the other files as references. A hidden folder (.name) is never a skill.
  • Each skill is validated against skillDocumentSchema. An invalid one is warned about and skipped.
  • On a name two sources share, the earlier source wins.
  • Sources are read once per graph build. A source with version() rebuilds the graphs on the next run once it changes. The directory source does this on any file’s size or modification time, so a skill saved while the process runs reaches the next run.

An agent or subagent that holds skills can call read_file even without it in builtinTools, but only on its own skills mount (/skills/, or /subagent-skills/<n>/ for a subagent). Any other path is refused. read_file in builtinTools has no such limit.

On a shared server you often want three levels: skills shipped with the host, skills the operator adds for everyone, and skills each tenant owns.

PilotooModule.forRoot({
skillLayers: { globalDir: '/srv/skills-global', tenantDir: '/srv/skills-tenants' },
});
// or AGENT_GLOBAL_SKILLS_DIR=/srv/skills-global AGENT_TENANT_SKILLS_DIR=/srv/skills-tenants
Layer Directory Written by
system AGENT_SKILLS_DIR, shipped with the host image Nothing at run time (config tools only when no tenant layer is set).
global skillLayers.globalDir / AGENT_GLOBAL_SKILLS_DIR The operator, by hand on the host. Never by a tool.
tenant skillLayers.tenantDir / AGENT_TENANT_SKILLS_DIR, one folder per tenant: <tenantDir>/<tenant>/<skill>/ config_save_skill, for the caller’s tenant only.

On a name two layers share, tenant wins over global, and global over system. The sources are ordered tenant, global, system, then skillSources. Each skillLayers field wins over its variable.

  • A source’s listSkills(identity) and version(identity) receive the caller. A tenant’s skills are listed, loaded into a graph, offered by GET /agent-admin/options and accepted in an agent’s runtime.skills only for callers of that tenant.
  • The runner’s graph cache is keyed by tenant, so one tenant’s graph never carries another tenant’s skills.
  • A tenant id that is not one path segment of letters, digits, _, - and . (not starting with .) is refused with InvalidTenantError and never used as a path.

Outside the module, use layeredSkillSources({ systemDir, globalDir, tenantDir }), createTenantSkillSource(tenantDir) and createTenantSkillStore(tenantDir, { fallback }).

A host that names neither directory has the skills directory alone. The local backend always does.

  • Config tools let an agent create skills for the person it works for.
  • Agents for runtime.skills.
  • Model gateway serves the global and tenant layers to a local backend.