Skip to Content
DocsSkillsLayered sources

Layered sources

Skills layer cleanly across system / user / project sources. Pass a list of paths to skills=; later sources override earlier ones by skill name.

from loomflow import Agent agent = Agent( "...", model="claude-opus-4-7", skills=[ "~/.jeeves/skills/system/", # base layer "~/.jeeves/skills/user/", # user customizes "./.jeeves-skills/", # project-local override ], )

If ~/.jeeves/skills/system/git-discipline/SKILL.md defines a git-discipline skill, and ./.jeeves-skills/git-discipline/SKILL.md also defines one with the same name, the project’s wins. The system version is shadowed entirely. Body, bundled files, and any tools.

Labelled sources

Any source can be tagged with a human-friendly label by passing a (path, label) tuple. The label shows up in skill listings and tool prefixes:

agent = Agent( skills=[ ("~/.jeeves/skills/system/", "System"), ("~/.jeeves/skills/user/", "User"), ("./.jeeves-skills/", "Project"), ], )

Auto-namespacing prevents collisions

Tool names get prefixed with the skill name automatically:

Skill shipsRegistered as
add (Mode C, in calc skill)calc__add
say_hi (Mode B, in greeter skill)greeter__say_hi
search (in two skills a and b)a__search and b__search. No clash

The double underscore (__) is the namespace separator. Inside the skill, you write the un-prefixed name; the prefix is added at registration.

Source order matters

The framework loads sources left-to-right. For each source, every discovered skill (one directory per skill, plus inline Skill instances) is added to the registry. When a name collides with a previously-loaded skill, the new one replaces the old.

This means:

  • First-loaded source = lowest priority (gets overridden).
  • Last-loaded source = highest priority (wins on collision).

Standard convention:

  1. system/. Defaults shipped with the framework / your platform.
  2. user/. What the operator customizes.
  3. project/. What the codebase pins.

Inline skills mix freely

You can combine path-based sources with inline Skill instances in the same list:

from loomflow import Agent from loomflow.skills import Skill agent = Agent( "...", model="claude-opus-4-7", skills=[ "~/.jeeves/skills/system/", Skill.from_text(""" --- name: tone description: Always reply in a brief, factual tone. --- Stay under 100 words. No filler. """), "./.jeeves-skills/", ], )

The inline skill obeys the same override rules. If ./.jeeves-skills/ also ships a tone skill, the project version wins.

Override is by NAME, not path. Two skills with different names in different sources both load. Two skills with the same name in different sources. The later source wins, the earlier is dropped.

Last updated on