原始内容
skilled-labour
A pi extension that makes small models actually load skills by leveraging their ability to issue tool calls.
How skills work
Pi implements a skills system. At startup, it builds an <available_skills>
block, summarizing each discovered skill's name and when to load, and telling
the model where the skill is to be found.
The model is then told (in text) to:
- consider whether any skill(s) are relevant to present tasks
- load those skill(s) by calling the
readtool to read theSKILL.mdfile first- (potentially reading supporting files, depending on the skill)
The problem
- Models excel at what they were trained on.
- Even small models are trained heavily on tool-calling
- Local models are bad at using skills; probably no trained to do so
The solution
Make loading a skill a tool call - leverage existing strengths
What skilled-labour does
- Strips pi's skills block and instructions from the system prompt.
- Appends a compact replacement carrying only skill names and descriptions.
- Registers a
load_skill(NAME)tool for the LLM to load a skill- the tool resolves the path to the skills
SKILL.mdand loads it - the response explicitly tells the LLM the skill's directory so referenced supporting files can be loaded if need be.
- the tool resolves the path to the skills
load_skill is idempotent: a re-call for an already-loaded skill no-ops (so
the model's reflexive re-calls don't bloat context), and session_compact
clears the "loaded" set so a post-compaction call re-injects the full body
exactly when re-injection is needed.
Install
Place this directory in your pi extensions folder:
- Linux/MacOS:
~/.pi/agent/extensions - Windows:
%USERPROFILE%\.pi\agent\extensions\
Implementation Details
Before - (default SKILLs implementation)
Injected into the system prompt verbatim. One <skill> block per skill, each
carrying a <location> path the model is expected to copy into a read call:
The following skills provide specialized instructions for specific tasks.
Use the read tool to load a skill's file when the task matches its description.
When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.
<available_skills>
<skill>
<name>odin-programming</name>
<description>Write, create, and edit Odin programs...</description>
<location>/home/nixos/.pi/agent/skills/odin-programming/SKILL.md</location>
</skill>
<skill>
<name>nix-flakes-editor</name>
<description>Write, create, and edit Nix flakes...</description>
<location>/home/nixos/.pi/agent/skills/nix-flakes-editor/SKILL.md</location>
</skill>
</available_skills>
The model is then expected to read prose instructions and spontaneously issue
a read tool call against one of those <location> paths. Small models
don't.
After — (skilled-labour)
The verbose block is stripped and replaced with a compact one-line-per-skill
section (no <location> — the tool resolves it) plus a pointer to load_skill:
<available_skills>
<skill name="odin-programming">Write, create, and edit Odin programs...</skill>
<skill name="nix-flakes-editor">Write, create, and edit Nix flakes...</skill>
</available_skills>
Call the load_skill tool with a skill's name to load its full instructions, BEFORE acting on a task the skill covers. It is cheap and strongly recommended. If a skill's instructions are already in the conversation above (loaded earlier this session), do not reload it.
And load_skill is registered as a real tool whose description and guidelines
carry the same nudge — reconsidered by the model every turn:
pi.registerTool({
name: "load_skill",
description:
"Load a skill's full instructions by name. Call this BEFORE acting on any task that a listed skill covers — it is cheap (one call) and nearly always improves results. No-op if the skill is already loaded. Pass the exact skill name from the <available_skills> list in the system prompt.",
promptGuidelines: [
"Before acting on a task, check the <available_skills> list in the system prompt. If any skill matches the task, call load_skill with its name FIRST, then follow the instructions it returns. Do not write code or run commands for a skill-covered task without loading the skill first.",
],
parameters: Type.Object({
name: Type.String({ description: 'Exact skill name from the <available_skills> list, e.g. "odin-programming".' }),
}),
async execute(_toolCallId, params) { /* resolve name -> read file -> return body */ },
});