Skip to content

Skills reference

Two kinds of skills

haiku.skills supports two distinct skill types:

Filesystem skills are directories following the Agent Skills specification. They contain a SKILL.md file and optional scripts/, references/, and assets/ directories. Scripts are executed as subprocesses via the run_script tool (sub-agent mode) or run_skill_script tool (direct mode). Portable and shareable — no Python packaging required.

Entrypoint skills are Python packages that provide typed tools running in-process. They support per-skill state, standard dependency management, and zero-config discovery via Python entrypoints. All example skills that ship with haiku.skills are entrypoint packages.

SKILL.md format

Both skill types use a SKILL.md file for metadata and instructions, following the Agent Skills specification. The file uses YAML frontmatter and markdown:

---
name: my-skill
description: A brief description of what the skill does.
---

# My Skill

Detailed instructions go here. In sub-agent mode, this content becomes
the sub-agent's system prompt. In direct mode, it is returned by query_skill.

Frontmatter fields

Field Type Required Description
name string yes Skill name (1-64 chars, lowercase alphanumeric + hyphens). Must match the directory name for filesystem skills.
description string yes What the skill does (1-1024 chars). Shown to the main agent in the skill catalog.
license string no License identifier (e.g. "MIT", "Apache-2.0").
compatibility string no Compatibility notes (max 500 chars).
metadata map no Arbitrary key-value pairs (string: string).
allowed-tools list or string no Tool names the agent may use. Accepts a YAML list or a space-separated string ("search fetch").

Unknown fields are rejected.

Signing

Skills can be signed with sigstore for identity-based verification. See Signing and verification for details.

Validate a skill directory against the spec:

haiku-skills validate ./my-skill

Script tools (filesystem skills)

Filesystem skills can include executable scripts in a scripts/ directory. In sub-agent mode, the sub-agent receives a run_script tool. In direct mode, the main agent uses run_skill_script. Both execute scripts as subprocesses.

Scripts should use named flags (--flag value) and support --help, following the Agent Skills script conventions.

Script resolution

The run_script tool expects a relative path under scripts/ (e.g. scripts/extract.py). Paths that escape the scripts/ directory are rejected. The execution method depends on the file extension:

Extension Executor
.py Current Python interpreter (sys.executable)
.sh bash
.js node
.ts npx tsx
Other Run as executable directly

The skill directory is prepended to PYTHONPATH, so Python scripts can import sibling modules.

In-process tools (entrypoint skills)

Entrypoint skills provide typed Python functions as tools. These run in the same process and can access per-skill state:

from haiku.skills import Skill, SkillToolset, build_system_prompt
from pydantic_ai import Agent


def add(a: float, b: float) -> float:
    """Add two numbers."""
    return a + b


skill = Skill(
    metadata=SkillMetadata(
        name="calculator",
        description="Perform mathematical calculations.",
    ),
    instructions="Use the add tool to add numbers.",
    tools=[add],
)

toolset = SkillToolset(skills=[skill])
agent = Agent(
    "anthropic:claude-sonnet-4-5-20250929",
    instructions=build_system_prompt(toolset.skill_catalog),
    toolsets=[toolset],
)

Reconfiguring entrypoint skills

Entrypoint skills discovered via SkillToolset(use_entrypoints=True) store their factory function. Call reconfigure(**kwargs) to re-invoke the factory with new arguments, replacing tools, state, model, instructions, and resources while preserving metadata. This is useful when a skill's factory accepts optional parameters (e.g. config, database path) that the entry point loader doesn't pass:

from haiku.skills import SkillToolset

toolset = SkillToolset(use_entrypoints=True)
skill = toolset.registry.get("my-rag-skill")
skill.reconfigure(config=my_config, db_path=my_db_path)

reconfigure() re-invokes the factory with the given keyword arguments and replaces the skill's tools, state, model, instructions, and resources in place. Metadata, source, path, and verified status are preserved — the skill keeps its identity in the registry.

This only works for entrypoint skills — filesystem and MCP skills have no factory and will raise RuntimeError.

Per-skill model override (sub-agent mode)

In sub-agent mode, individual skills can specify their own model, overriding the skill_model set on SkillToolset:

skill = Skill(
    metadata=SkillMetadata(name="heavy-reasoning", description="..."),
    instructions="...",
    tools=[...],
    model="openai:gpt-4o",  # this skill always uses gpt-4o
)

The resolution order is: skill model > SkillToolset.skill_model > pydantic-ai default.

In direct mode, skill tools run in the main agent's context, so these model overrides have no effect.

Toolsets

For AbstractToolset instances (e.g. MCP toolsets), use the toolsets parameter instead of tools. See the Tutorial — MCP skills section for details.

Resources

Skills automatically discover resource files — any non-script, non-Python file in the skill directory. For filesystem skills, resources are discovered during path scanning. For entrypoint skills, set path in the factory and resources are discovered automatically. In sub-agent mode, the sub-agent receives a read_resource tool; in direct mode, the main agent uses read_skill_resource.

  • Resolved paths must stay within the skill directory (traversal defense).
  • Files must be text — binary files raise an error.

Extras

Skills can carry arbitrary non-tool data via extras. This is useful for exposing utility functions or other resources that the consuming app needs but that aren't agent tools:

def calculate_calories(ingredient: str, grams: float) -> float:
    ...

skill = Skill(
    metadata=SkillMetadata(name="recipes", description="Recipe search."),
    instructions="...",
    tools=[...],
    extras={"calculate_calories": calculate_calories},
)

The app discovers skills via entrypoints and accesses extras by name:

from haiku.skills.discovery import discover_from_entrypoints

skills = {s.metadata.name: s for s in discover_from_entrypoints()}
skill = skills["recipes"]
calories = skill.extras["calculate_calories"]("flour", 200)

Per-skill state

Entrypoint skills can declare a Pydantic state model. State is passed to tool functions via RunContext[SkillRunDeps] and tracked per namespace on the toolset:

from pydantic import BaseModel
from pydantic_ai import RunContext

from haiku.skills import Skill, SkillMetadata, SkillRunDeps, SkillToolset


class CalculatorState(BaseModel):
    history: list[str] = []


def add(ctx: RunContext[SkillRunDeps], a: float, b: float) -> float:
    """Add two numbers."""
    result = a + b
    if ctx.deps and ctx.deps.state and isinstance(ctx.deps.state, CalculatorState):
        ctx.deps.state.history.append(f"{a} + {b} = {result}")
    return result


skill = Skill(
    metadata=SkillMetadata(
        name="calculator",
        description="Perform mathematical calculations.",
    ),
    instructions="Use the add tool to add numbers.",
    tools=[add],
    state_type=CalculatorState,
    state_namespace="calculator",
)

toolset = SkillToolset(skills=[skill])
print(toolset.build_state_snapshot())  # {"calculator": {"history": []}}

When a skill's tools modify state, the toolset computes a JSON Patch delta and returns it as a StateDeltaEvent — compatible with the AG-UI protocol. This works in both sub-agent and direct mode. See AG-UI protocol for details.

Introspecting state

Use state_metadata() to inspect a skill's state configuration without running it:

meta = skill.state_metadata()
# StateMetadata(namespace="calculator", type=<class 'CalculatorState'>, schema={...})

Returns None for skills without state.

Skills can also declare a deps_type to extend the default SkillRunDeps with additional fields for toolsets or lifespan-managed resources. See Custom dependencies.