skill
js/ai/skill.ts
fino:ai/skill — lazy instructions, resources, and tools for agents.
Skills package optional capabilities behind a manifest. An agent configured
with a SkillRegistry sees only each skill's name and description in its
system prompt, then can call the generated load_skill tool to fetch the
full instructions, resources, and bundled tools when they are relevant.
Design
This keeps large or specialized instructions out of the default prompt while preserving discoverability. Skill instructions and resources may be static or loaded lazily. Tool definitions bundled with a skill are registered after the skill is loaded, and a fresh agent over the same messages can re-register those tools from the load result for resume safety.
Remote SkillsMD skills use the same registry path as local skills. A skill
declared with repo: 'owner/repo' appears in the manifest immediately using
placeholder metadata, then fetches and parses the repository's skill markdown
only when load_skill asks for it.
Skills are not a sandbox or trust boundary. Only register skills from code you trust, and treat loaded instructions as part of the prompt surface.
import { agent } from 'fino:ai/agent';
import { openai } from 'fino:ai/model';
import { skill, skillRegistry } from 'fino:ai/skill';
const registry = skillRegistry([
skill({
name: 'support',
description: 'Support-ticket writing guidance.',
instructions: 'Ask for missing account details before promising refunds.',
tools: {},
}),
skill({ repo: 'acme/support-writing' }),
]);
const bot = agent({ model: openai({ model: 'gpt-4o' }), skills: registry });
Interfaces
interface SkillResource {
Named resource bundled with a skill.
When a skill is loaded, every resource is resolved and appended to the
skill's instructions under a ## Resources heading, each as its own
### name section. Because resolution only happens at load time, resources
are a good place for large reference material that should not weigh on the
agent until the skill is actually needed.
import { skill } from 'fino:ai/skill';
const research = skill({
name: 'research',
description: 'Research briefing workflow.',
instructions: 'Summarize the brief before proposing next steps.',
resources: [
{ name: 'style.md', content: '# Style\nWrite in plain language.' },
{
name: 'brief.md',
content: async () => (await fetch('https://example.com/brief.md')).text(),
},
],
});
Properties
name: string
Heading the resource content is filed under in the loaded instructions.
content: string | (() => Promise<string>)
Resource text, either inline or produced lazily when the skill loads.
A function form is awaited once per registry cache entry; concurrent loads of the same skill share the result.
interface Skill {
Lazy-loadable agent capability.
Only name and description are surfaced to an agent up front (via the
registry manifest); instructions, resources, and tools stay dormant
until the skill is loaded. Construct skills with skill() rather than
implementing this interface by hand — remote skills carry internal state
that only skill() wires up.
import { skill, skillRegistry } from 'fino:ai/skill';
import type { Skill } from 'fino:ai/skill';
const reviewer: Skill = skill({
name: 'review',
description: 'Code-review checklist and tone.',
instructions: async () => 'Point out risky changes before style nits.',
});
const registry = skillRegistry([reviewer]);
Readonly Properties
readonly name: string
Unique identifier the agent passes to load_skill.
For remote skills this starts as the repo name and may be replaced by
the fetched frontmatter's name after the first load.
readonly description: string
Short summary shown in the agent's system prompt before the skill loads.
This is the model's only signal for deciding whether to load the skill, so make it concrete about when the skill applies.
readonly instructions: string | (() => Promise<string>)
Full guidance revealed when the skill is loaded.
A function form is awaited lazily at load time, which is how remote skills defer their network fetch.
readonly tools: Record<string, Tool>
Tools the agent registers once the skill is loaded.
Remote skills always have an empty tool map — markdown fetched from a repository never yields executable code.
readonly resources: SkillResource[]
Reference material appended to the instructions at load time.
interface SkillsMdOptions {
Options for SkillsMD-backed remote skills.
All fields are optional; the defaults talk to the public SkillsMD API with
the global fetch and a five-minute instruction cache.
import { skill } from 'fino:ai/skill';
const remote = skill({
repo: 'acme/support-writing',
skillsmd: {
apiBaseUrl: 'https://skillsmd.internal/api',
cacheTtlMs: 60_000,
},
});
Properties
apiBaseUrl?: string
Base URL for the SkillsMD API.
Defaults to https://skillsmd.dev/api.
fetch?: typeof globalThis.fetch
Fetch implementation used for SkillsMD and GitHub requests.
Tests and embedded runtimes can inject a custom implementation.
cacheTtlMs?: number
Registry cache duration for loaded remote skill instructions.
Defaults to five minutes. Local skills continue to use the registry's normal stable cache.
interface SkillRegistry {
Registry of skills available to an agent.
Pass a registry as skills when constructing an agent: the agent puts
manifest() in its system prompt, registers asLoaderTool() so the model
can pull skills in on demand, and calls load() to re-register bundled
tools for any load_skill calls it finds in resumed message history.
Load results are cached per registry. Local skills are cached for the registry's lifetime; remote skills expire after their configured TTL. Concurrent loads of the same skill share one in-flight promise, and a failed load is evicted so the next attempt retries from scratch.
import { skill, skillRegistry } from 'fino:ai/skill';
const registry = skillRegistry()
.add(skill({
name: 'billing',
description: 'Refund and invoice policies.',
instructions: 'Never promise a refund without an order id.',
}))
.add(skill({ repo: 'acme/support-writing' }));
registry.manifest(); // [{ name: 'billing', ... }, { name: 'support-writing', ... }]
const { instructions, tools } = await registry.load('billing');
Methods
add(...skills: Skill[]): this
Register additional skills, replacing any existing skill with the same name. Returns the registry so calls can be chained.
manifest(): {
name: string;
description: string;
}[]
List the name and description of every registered skill — the only metadata an agent sees before loading.
Remote skills that have not been loaded yet report placeholder metadata derived from their repo identifier; their entries update once a load fetches the real frontmatter.
load(name: string): Promise<{
instructions: string;
tools: Record<string, Tool>;
}>
Resolve a skill to its full instructions (resources appended) and bundled tools, fetching remote markdown if needed.
Throws if no skill with that name is registered, and rejects if a remote skill's markdown cannot be fetched or parsed.
asLoaderTool(): Tool
Build the load_skill tool the agent exposes to the model.
The tool takes a skill name, delegates to load(), and returns the
loaded instructions as its result; the agent runtime takes care of
registering the skill's bundled tools for subsequent steps.
interface LocalSkillDefinition {
Local skill definition accepted by skill().
Everything about a local skill is supplied in code: metadata, instructions (inline or lazy), and optionally executable tools and resources.
import { skill } from 'fino:ai/skill';
import { tool } from 'fino:ai/tool';
import { v } from 'fino:validate';
const deploys = skill({
name: 'deploys',
description: 'Rollout and rollback procedures.',
instructions: 'Check the error budget before any rollout.',
tools: {
rollback: tool({
name: 'rollback',
description: 'Roll a service back to the previous release.',
parameters: v.object({ service: v.string() }),
execute: async ({ service }) => `rolled back ${service}`,
}),
},
});
Properties
name: string
Unique skill name; also the argument agents pass to load_skill.
description: string
One-line summary shown in the agent's system prompt.
instructions: string | (() => Promise<string>)
Full guidance, inline or produced lazily when the skill first loads.
tools?: Record<string, Tool>
Tools registered with the agent once the skill loads. Defaults to none.
resources?: SkillResource[]
Reference material appended to the instructions at load time.
interface RemoteSkillDefinition {
SkillsMD-backed remote skill definition accepted by skill().
Only repo is required. The skill appears in the registry manifest
immediately with placeholder metadata and fetches the repository's skill
markdown on first load. Instructions are the markdown body below the YAML
frontmatter; remote skills never carry executable tools or resources.
import { skill } from 'fino:ai/skill';
const remote = skill({
repo: 'acme/support-writing',
description: 'House style for support replies.',
});
Properties
repo: string
GitHub owner/repo identifier of the skill repository.
Must be a bare identifier — URLs and extra path segments are rejected.
name?: string
Manifest name override. When omitted, the repo name is used until the fetched frontmatter supplies one; when set, frontmatter never replaces it.
description?: string
Manifest description override. Same precedence as name: an explicit
value is never replaced by fetched metadata.
skillsmd?: SkillsMdOptions
Transport and caching options for the SkillsMD fetch.
Functions
function skill(def: LocalSkillDefinition | RemoteSkillDefinition): Skill
Create a skill definition.
A definition with a repo field builds a remote SkillsMD skill; anything
else is a local skill. Local skills are returned essentially as given, with
tools and resources defaulting to empty. Remote skills defer all network
work to load time: loading queries the SkillsMD API, falls back to the
repository's skill.md/SKILL.md via the GitHub contents API, then to a
README with frontmatter, and rejects if no frontmattered markdown is found
anywhere.
Throws immediately if a remote repo is not a bare owner/repo identifier.
import { skill } from 'fino:ai/skill';
const local = skill({
name: 'triage',
description: 'Incident triage steps.',
instructions: async () => 'Page the on-call before mitigating.',
});
const remote = skill({ repo: 'acme/incident-runbooks' });
function skillRegistry(skills?: Skill[]): SkillRegistry
Create a registry of skills that an agent can load on demand.
The optional initial list can be extended later with add(). Local and
remote skills mix freely in one registry and load through the same
load_skill tool.
import { agent } from 'fino:ai/agent';
import { openai } from 'fino:ai/model';
import { skill, skillRegistry } from 'fino:ai/skill';
const registry = skillRegistry([
skill({
name: 'billing',
description: 'Refund and invoice policies.',
instructions: 'Never promise a refund without an order id.',
}),
]);
const bot = agent({ model: openai({ model: 'gpt-4o' }), skills: registry });