Skills
Procedurally-disclosed knowledge that teaches agent roles how to handle specific situations.
Little Workflow: published on npm as little-workflow@0.2.0-alpha.1 (2026-10-06). Sections labeled UNRELEASED describe source-only changes. Release status
A Skill is a piece of procedural knowledge for an agent role — "how to design back-edge loops in LWIR", "how to validate output schemas", "candidate-review patterns from prior runs". Skills use progressive disclosure: the description is always in the system prompt (cheap); the full body is loaded on demand via bash when the agent decides it is relevant.
Skills are files the developer authors alongside their workflow. The SDK mounts them into the Harness session; the agent reads them when useful.
Related but distinct. Skills (like sessions and persistent dirs) are canonically a Harness concept — the
SKILL.mdformat, progressive disclosure, and staging are defined by Little Harness. See the Little Harness skills docs for the format itself. This page covers only how Little Workflow scopes skills to the planner, orchestrator, worker, and fixer roles.
Format
Directory form (primary)
my-workflow/
└── skills/
└── writing-loop-lwir/
├── SKILL.md ← frontmatter + body
├── examples/
│ ├── candidate-review.json
│ └── document-review.json
└── anti-patterns.mdSKILL.md is the entry point. Auxiliary files live alongside; the agent can cat them when relevant.
SKILL.md shape
---
name: writing-loop-lwir
description: How to design back-edge loops with decision steps for iterative refinement (worker → review → route).
---
# Writing Loop LWIR
Use a back-edge loop when the workflow needs iterative refinement...
## Pattern
The loop has three steps:
- A worker step that produces a candidate.
- A reviewer step that evaluates it.
- A decision step that either ends ("end") or routes back for another iteration.
## Common mistakes
- Forgetting maxVisits >= 2 on the worker step.
- Using steps.review.output instead of steps.review.lastOutput.Single-file shorthand
my-workflow/
└── skills/
└── output-schema-validation.md ← frontmatter + body in one fileThe file must start with YAML frontmatter:
---
name: output-schema-validation
description: How to validate ai.generate output against declared schemas.
---
# Output Schema Validation
[body]Use single-file for simple skills. Directory form is preferred when the skill has examples or references.
Required frontmatter fields
| Field | Type | Notes |
|---|---|---|
name | string | Slug; unique within a session's skill set. Matches the directory name or file basename. |
description | string | One-line summary. Auto-loaded into the system prompt at session start. |
Optional: model (suggested quality tier hint; not enforced), allowed-tools (hint to the agent; not enforced).
The skill() factory
import { skill } from "@little-stack/workflow";
skill("./skills/writing-loop-lwir/") // local directory
skill("./skills/output-schema-validation.md") // single file
skill("https://github.com/anthropics/skills", {
skills: ["frontend-design"],
}) // Git URL — selected skill namesSignature:
export function skill(pathOrUrl: string, options?: RemoteSkillOptions): Skill;The Skill is a lazy reference. The SDK resolves local paths and materializes remote Git skills in the role runtime before the session starts. Local skills can be a directory with SKILL.md or a single .md file.
Remote skills must use explicit Git URLs. GitHub URLs, GitLab tree URLs, generic HTTPS Git URLs, SSH URLs, and scp-style Git URLs are supported. Repository shorthands such as org/repo, github:org/repo, and gitlab:org/repo are rejected so the source and transport are unambiguous.
The skills option selects skill names from the remote repository, matching npx skills add URL --skill frontend-design. If skills is omitted, the runtime installs every non-internal skill discovered in the repository. To pin a version, include a commit SHA or ref in the Git URL or source options; the materialization cache is keyed by resolved commit SHA.
Remote Git skill loading is best-effort at runtime. If a remote repository cannot be fetched, a selected skill cannot be discovered, or a remote risk audit fails, the role session continues without that remote skill and records a warning on the harness session. Local skill path and metadata errors remain strict because they indicate a broken deployment artifact.
skill("https://github.com/anthropics/skills", {
skills: ["frontend-design"],
});
skill("https://gitlab.com/acme/agent-skills/-/tree/main", {
skills: ["customer-research"],
});
skill("git@github.com:acme/private-skills.git", {
skills: ["deal-review"],
auth: { type: "bearer", token: process.env.GIT_TOKEN },
});Private Git sources can pass explicit auth with auth: { type: "bearer", token: process.env.GIT_TOKEN }. As a host-level fallback, set LITTLE_SKILLS_GIT_TOKEN and restrict where it is sent with LITTLE_SKILLS_GIT_TOKEN_HOSTS.
Risk gates are optional. If your deployment configures one, set skillOidcToken: process.env.VERCEL_OIDC_TOKEN so the runtime can call the Skills API. skillMaxRisk can be set globally, per role, or on a specific skill(...) source. For selected remote skills, skillRisk can assign per-skill policy inside that skill(...) call.
Loading semantics
At session start
The SDK reads all skill frontmatter for the session's configured skills (workflow-declared and org-mounted) and appends a skill list to the system prompt:
Available skills (read via `cat .agents/skills/<name>/SKILL.md`):
- writing-loop-lwir: How to design back-edge loops with decision steps for iterative refinement.
- output-schema-validation: How to validate ai.generate output against declared schemas.
- pii-handling: How to handle personally identifiable information in workflow inputs.The description is in the system prompt; the body is not. The agent knows what skills exist and what they cover without paying tokens for the body upfront.
On-demand body loading
When the agent decides a skill is relevant, it reads the body via bash:
cat .agents/skills/writing-loop-lwir/SKILL.mdThe output enters the next turn's context. Auxiliary files in a directory skill can also be read:
ls .agents/skills/writing-loop-lwir/
# SKILL.md examples/ anti-patterns.md
cat .agents/skills/writing-loop-lwir/examples/candidate-review.jsonNo dedicated read_skill tool exists — bash is sufficient and keeps the tool surface minimal.
Mount path
.agents/skills/<name>/ ← materialized skill directory
.agents/skills/<name>/SKILL.mdSkills are staged read-only under .agents/skills/<name>/ from the role/root working directory. Directory-form and single-file local skills both materialize to this directory shape so the prompt can always point at SKILL.md.
Role applicability
Skills follow the model. Where a model runs in an agent loop, skills are useful. Where execution is direct (no model), skills are not relevant.
| Role | Skills available |
|---|---|
| Planner | planner.skills (workflow-declared) + org-mounted |
| Orchestrator | orchestrator.skills (run-declared) + org-mounted |
Worker — ai.generate steps | worker.skills (workflow-declared) + org-mounted |
Worker — code.run steps | None (no model in the loop) |
Worker — tool.call steps | None (no model in the loop) |
| Fixer | Inherits worker.skills + org-mounted |
The Fixer inherits from worker.skills because it is fixing a failed worker step — the same domain knowledge applies. Per-step fixer skill overrides are post-alpha.
In the default worker harness an ai.generate step is one model call with no bash, so it sees the skill list (names and descriptions) but cannot read a skill body. Put instructions the step must follow in its with.system or with.prompt.
Declaring skills on roles
Skills are declared on each role's config, not as a top-level workflow field.
import { defineWorkflow, runOrchestration, skill } from "@little-stack/workflow";
import { openai } from "@ai-sdk/openai";
const candidateReview = defineWorkflow({
id: "candidate.review",
// ...
planner: {
model: openai("gpt-5"),
system: "You design candidate-review workflows.",
skills: [
skill("./skills/writing-loop-lwir/"),
skill("./skills/output-schema-validation.md"),
],
},
worker: {
skills: [skill("./skills/ai-step-best-practices/")],
},
});
// Orchestrator skills (deprecated surface) are declared on runOrchestration:
await runOrchestration({
world,
workflows: [candidateReview, candidateScoring],
input,
tools,
orchestrator: {
model: openai("gpt-5"),
system: "Orchestrate candidate evaluation.",
skills: [skill("./skills/batch-fan-out/")],
},
});Sources
Skills come from two sources, materialized before each role session starts:
1. Org-mounted skills
Files under dataDir/memory/org/skills/. Curated by an org admin, or written by a dreaming workflow that consolidates lessons from many runs. Available to all roles across all workflows in the same dataDir. Read-only.
2. Workflow-declared skills
Declared on the role config (planner.skills, worker.skills, orchestrator.skills). Local paths, or explicit Git URL references that the runtime materializes before the role session starts.
Run-time injection (a skills option on runWorkflow for all roles) is reserved for post-alpha.
Conflict resolution
Two skills with the same name from different sources:
Workflow-declared overrides org-mounted. If both declare name: output-schema-validation, the workflow's version wins. A warning is logged during skill materialization:
Skill 'output-schema-validation' from workflow overrides org-level skill of same name.Same name within one source is an error. Two workflow-declared skills with the same name throw before the role session starts:
Error: Skill name 'loops' declared twice.Example: planner system prompt assembly
Given the configuration above, the planner session sees:
[Developer system prompt]
You design candidate-review workflows.
[Role addendum — SDK-injected]
You are the Planner. Use bash to read memory and scratch. ...
[Skill list — SDK-injected]
Available skills (read via `cat .agents/skills/<name>/SKILL.md`):
- writing-loop-lwir: How to design back-edge loops with decision steps for iterative refinement.
- output-schema-validation: How to validate ai.generate output against declared schemas.
- pii-handling: How to handle personally identifiable information in workflow inputs.
[Memory/scratch mounts]
...The planner's agent loop:
# Decide the loop pattern applies; load the skill:
cat .agents/skills/writing-loop-lwir/SKILL.md
# Browse examples:
ls .agents/skills/writing-loop-lwir/examples/
cat .agents/skills/writing-loop-lwir/examples/candidate-review.json
# Apply the pattern to draft the LWIR.The body is read once per session. Future turns don't re-read — it is already in context.
Not in alpha
- Run-time skill injection — a
skillsoption onrunWorkflowinjecting for all roles. Alpha supports workflow-declared and org-mounted only. - Skill versioning —
skill("foo@1.2.0")semver pinning. read_skill(name)dedicated tool — bash is sufficient.- Dynamic skill discovery at runtime. The skill list is fixed at session start.
code.runaccess to skills — direct-execute steps have no model. If you need skill-driven code generation, that is the planner's job.- Dreaming-generated skills — a dreaming workflow (post-alpha) can consolidate run lessons into new org-level skills. The storage layer is ready; the dreaming workflow itself is post-alpha.
Cross-references
- Memory — org skills live under
dataDir/memory/org/skills/ - Orchestrator — configuring
orchestrator.skills - Workflow Config — workflow definition and planner config