Skip to content

teff.skill

teff.skill

Skills — reusable instruction + tool-scope bundles loaded from folders.

Skills follow the open Agent Skills layout: <name>/SKILL.md holds YAML frontmatter plus markdown instructions. A skill contributes instructions to any LLM call (LLM node, ReActAgent/harness) and can narrow which tools that call may use via allowed-tools / disallowed-tools.

Example::

skills/data-analysis/SKILL.md
    ---
    name: data-analysis
    description: Answer questions over tabular data
    allowed-tools: [csv_query, plot]
    ---
    You are a data analyst.  When asked about numbers, always query the
    CSV first with the csv_query tool, then answer from its output.

flow.react(model="llama3.1:8b", skills=["data-analysis"], skill_dir="skills")

Classes:

Name Description
Skill

A loadable skill bundle.

Functions:

Name Description
core_skills

Return the built-in core (teff-) skills.

get_core_skill

Return a core skill by name, or None if it does not exist.

load_skill

Load a skill from a folder or a SKILL.md file.

resolve_skills

Resolve cfg["skills"] into a list of loaded skills.

scope_tools

Filter the tool pool to what a node / its skills may use.

skills_instructions

Render skill instructions as a single block for the system prompt.

Skill dataclass

A loadable skill bundle.

Attributes:

Name Type Description
name str

Skill name (defaults to the folder name).

description str

What it does and when to use it.

when_to_use str

Optional extra routing hints.

instructions str

Markdown body injected into the system prompt.

allowed_tools list[str] | None

If set, only these tools are visible to the call.

disallowed_tools list[str]

Tools removed from the visible set.

path Path | None

Directory the skill was loaded from (None if synthetic).

builtin bool

True for skills bundled with teff (core/teff- skills), False for user skills loaded from disk.

Source code in teff/skill.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@dataclass
class Skill:
    """A loadable skill bundle.

    Attributes:
        name: Skill name (defaults to the folder name).
        description: What it does and when to use it.
        when_to_use: Optional extra routing hints.
        instructions: Markdown body injected into the system prompt.
        allowed_tools: If set, only these tools are visible to the call.
        disallowed_tools: Tools removed from the visible set.
        path: Directory the skill was loaded from (``None`` if synthetic).
        builtin: True for skills bundled with teff (core/``teff-`` skills),
            False for user skills loaded from disk.
    """

    name: str
    description: str = ""
    when_to_use: str = ""
    instructions: str = ""
    allowed_tools: list[str] | None = None
    disallowed_tools: list[str] = field(default_factory=list)
    path: Path | None = None
    builtin: bool = False

core_skills

core_skills()

Return the built-in core (teff-) skills.

Source code in teff/skill.py
137
138
139
def core_skills() -> list[Skill]:
    """Return the built-in core (``teff-``) skills."""
    return list(_CORE_SKILLS)

get_core_skill

get_core_skill(name)

Return a core skill by name, or None if it does not exist.

Source code in teff/skill.py
142
143
144
145
146
147
def get_core_skill(name: str) -> Skill | None:
    """Return a core skill by name, or ``None`` if it does not exist."""
    for s in _CORE_SKILLS:
        if s.name == name:
            return s
    return None

load_skill

load_skill(path)

Load a skill from a folder or a SKILL.md file.

Parameters:

Name Type Description Default
path str | Path

Either a directory containing SKILL.md or the path to the SKILL.md file itself.

required

Returns:

Name Type Description
A Skill

class:Skill.

Raises:

Type Description
FileNotFoundError

If no SKILL.md is found at path.

Source code in teff/skill.py
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
def load_skill(path: str | Path) -> Skill:
    """Load a skill from a folder or a ``SKILL.md`` file.

    Args:
        path: Either a directory containing ``SKILL.md`` or the path to
            the ``SKILL.md`` file itself.

    Returns:
        A :class:`Skill`.

    Raises:
        FileNotFoundError: If no ``SKILL.md`` is found at *path*.
    """
    p = Path(path)
    if p.is_dir():
        p = p / "SKILL.md"
    if not p.is_file():
        raise FileNotFoundError(f"skill file not found: {p}")
    text = p.read_text(encoding="utf-8")
    front, body = _split_frontmatter(text)
    meta: dict = {}
    if front.strip():
        loaded = yaml.safe_load(front)
        if isinstance(loaded, dict):
            meta = loaded

    allowed = meta.get("allowed-tools", meta.get("allowed_tools"))
    disallowed = meta.get("disallowed-tools", meta.get("disallowed_tools"))

    return Skill(
        name=str(meta.get("name") or p.parent.name),
        description=str(meta.get("description", "") or ""),
        when_to_use=str(meta.get("when_to_use", "") or ""),
        instructions=body.strip(),
        allowed_tools=_to_list(allowed) or None,
        disallowed_tools=_to_list(disallowed),
        path=p.parent,
    )

resolve_skills

resolve_skills(cfg)

Resolve cfg["skills"] into a list of loaded skills.

Each entry may be a :class:Skill, a path to a skill folder or SKILL.md file, or a bare name resolved against cfg["skill_dir"] (default "skills" relative to the current directory).

Source code in teff/skill.py
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
def resolve_skills(cfg: dict) -> list[Skill]:
    """Resolve ``cfg["skills"]`` into a list of loaded skills.

    Each entry may be a :class:`Skill`, a path to a skill folder or
    ``SKILL.md`` file, or a bare name resolved against ``cfg["skill_dir"]``
    (default ``"skills"`` relative to the current directory).
    """
    raw = cfg.get("skills") or []
    if isinstance(raw, (str, Path, Skill)):
        raw = [raw]
    skill_dir = Path(cfg.get("skill_dir", "skills"))

    skills: list[Skill] = []
    for item in raw:
        if isinstance(item, Skill):
            skills.append(item)
            continue
        p = Path(item)
        if p.is_file() or p.is_dir():
            skills.append(load_skill(p))
            continue
        candidate = skill_dir / str(item) / "SKILL.md"
        if candidate.is_file():
            skills.append(load_skill(candidate))
            continue
        core = get_core_skill(str(item))
        if core is not None:
            skills.append(core)
            continue
        msg = f"skill not found: {item}"
        raise FileNotFoundError(msg)
    return skills

scope_tools

scope_tools(pool, cfg, skills=None)

Filter the tool pool to what a node / its skills may use.

cfg["use_tools"] may be None or an empty list (nothing), "all" (everything), or a list of names to allow. (True/False are also honoured for backwards compatibility.) Skills narrow the set further: allowed_tools intersects with whatever the node allows, disallowed_tools removes tools outright.

Source code in teff/skill.py
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
def scope_tools(
    pool: "Mapping[str, Tool]", cfg: dict, skills: list[Skill] | None = None
) -> dict[str, Tool]:
    """Filter the tool pool to what a node / its skills may use.

    ``cfg["use_tools"]`` may be ``None`` or an empty list (nothing),
    ``"all"`` (everything), or a list of names to allow.  (``True``/``False``
    are also honoured for backwards compatibility.)  Skills narrow the set
    further: ``allowed_tools`` intersects with whatever the node allows,
    ``disallowed_tools`` removes tools outright.
    """
    use = cfg.get("use_tools")

    def _all() -> set[str]:
        return set(pool)

    if use is None:
        allowed: set[str] = set()
    elif isinstance(use, str):
        allowed = _all() if use.strip().lower() in ("all", "*") else {use}
    elif isinstance(use, (list, tuple, set)):
        allowed = {str(k) for k in use}
    elif isinstance(use, bool):
        allowed = _all() if use else set()
    else:
        allowed = set()

    for s in skills or []:
        if s.allowed_tools is not None:
            allowed &= set(s.allowed_tools)

    disallowed: set[str] = set()
    for s in skills or []:
        disallowed |= set(s.disallowed_tools)

    return {k: v for k, v in pool.items() if k in allowed and k not in disallowed}

skills_instructions

skills_instructions(skills)

Render skill instructions as a single block for the system prompt.

Source code in teff/skill.py
235
236
237
238
239
240
241
242
243
244
245
246
247
def skills_instructions(skills: list[Skill]) -> str:
    """Render skill instructions as a single block for the system prompt."""
    parts = []
    for s in skills:
        if not s.instructions:
            continue
        header = f"### Skill: {s.name}"
        if s.builtin:
            header += " [system]"
        if s.description:
            header += f" — {s.description}"
        parts.append(f"{header}\n\n{s.instructions}")
    return "\n\n".join(parts)