Load instructions on demand with skills
Two things share the word "skill" here, and they are unrelated:
Two things share the word "skill" here, and they are unrelated:
- This page:
psych_runtime.Skill, an instruction pack inside anAgentSpecthat the model being run loads on demand. .agents/skills/: files a coding agent reads to work with Psych. That is what you are reading now.
A Spec Skill exists because instruction packs are large and most of them are irrelevant to any given turn. Descriptions sit in the system prompt always; bodies load only when the model asks.
spec = psych_runtime.AgentSpec(
name="support",
instructions="Help the customer. Follow [[skill:refund-policy]] before refunding.",
model=psych_runtime.ModelRef(model="gpt-4o"),
skills=(
psych_runtime.Skill(
name="refund-policy",
description="When a refund is allowed, and the limits on one",
body="Refunds are allowed within 30 days of delivery...\n\n"
"For anything over £500 see [[skill:escalation]].",
),
psych_runtime.Skill(
name="escalation",
description="Who to escalate to, and what to include",
body="...",
),
),
)What the model sees
The system prompt gets an index of names and descriptions, never bodies:
## Skills
Instruction packs you can load when you need them. Call `load_skill` with a
name to read one. Load a skill before doing work it covers rather than guessing
at its content.
- `refund-policy`: When a refund is allowed, and the limits on one
- `escalation`: Who to escalate to, and what to includePutting bodies there would defeat the whole mechanism.
load_skill is registered automatically whenever a Spec has skills. You do not
wire it, and you cannot name a tool load_skill yourself: it is reserved.
Write the description for a router
The description is the only thing the model has when deciding whether to load. Write it as the condition under which the skill is relevant, not as a title.
"Refund rules" tells the model nothing about when to reach for it. "When a refund is allowed, and the limits on one" does.
[[skill:name]] links
Use them anywhere in instructions or in another skill's body. The pattern is
[[skill:name]] where the name matches [a-zA-Z_][a-zA-Z0-9_.-]{0,127}.
Publish-time validation walks the link graph and rejects a dangling link. By the time a Spec reaches the runtime, every link in every body names a skill that really exists. That is what publish-time validation is for: a customer waiting on a response is the wrong place to discover a typo.
When load_skill returns a body containing links, it mentions what the skill
leads to, so the model can follow the chain.
Loading the same skill twice
load_skill always returns the body, and only adds a note when this is a
repeat. Saying "you already loaded this" without returning the body would be
useless, because the reason it asked again is that the first answer fell out of
its context window.
That "already loaded" bookkeeping lives in process memory for one Attempt and
does not survive a crash, which is fine: the conversation rebuilt from the log
after a reclaim still contains every earlier load_skill call and its result,
so a model needing the body again simply calls again.
When to use one
Yes: a refund policy, a brand style guide, an incident runbook, an API convention document, anything long that applies to some turns and not others.
No: something needed on every turn, which belongs in instructions; a large
dataset, which belongs behind a tool; anything that changes more often than you
want to republish, since skills are part of the Spec and version with it.
Gotchas
- Skills are inside the Version hash. Editing a body produces a new Version. That is correct and it means skill text is reviewed like code.
descriptioncaps at 1024 characters andbodyhas no cap. Keep the description short; it is paid for on every turn.- Bodies and descriptions are whitespace-normalised on construction, so two Specs differing only in indentation hash the same.
- A skill is instructions, not tools. It cannot grant a tool. Grant tools on the Spec and let the skill explain when to use them.