Skip to main content
An agent that does one job needs one set of instructions. An agent that does the same job in two different contexts needs two — and hardcoding load_skill calls forces you to pick one, or to ship two agents. Skill sets solve that. You bundle a small library of skills with your agent, group them into named sets in gaia-agent.yaml, and let exactly one set activate per launch — chosen explicitly, or by whatever runtime signal your agent already has. This guide builds a worked example: a code-review agent that reviews differently for a library than for an application.
Prerequisites: you have an agent package with a gaia-agent.yaml — see Custom Agents. Background on the format itself is in Agent Skills and Skill Format.

1. Bundle the skills

Put each skill in its own directory inside your importable package, so the wheel and any frozen binary ship it:
A skills/ folder beside your package rather than inside it works from a source checkout and then vanishes from an installed wheel — the classic works-on-my-machine failure. Keep it inside the package and declare it as package data ([tool.setuptools.package-data]), plus --add-data if you freeze a binary.
A skill is a SKILL.md: frontmatter plus a Markdown body. Keep the body short — it is injected into the system prompt when the skill loads, and every token it takes is a token your tool results no longer have.
tools_required names registry tools the skill’s recipe consumes — it does not grant anything. A name your agent has not registered is logged at load time, so a skill that cannot execute its own recipe is diagnosable rather than silently useless.

2. Declare the sets

Three top-level keys in gaia-agent.yaml:
changelog-discipline is in both sets — sets overlap, they do not partition. The one thing a set may not do is re-declare a skill that is already in the always-on skills: list; that contradiction fails to parse. An entry is a plain name or a mapping:

3. Point the agent at both

That is enough. Agent.__init__ resolves a set and loads it — the always-on skills plus that set’s, in declaration order.

4. Choose the set at runtime

Selection resolves in one order, every time:
1

Explicit — `skill_set=`

MyReviewAgent(skill_set="library"), which your CLI surfaces as --skill-set library. Highest precedence, and never second-guessed.
2

Your selector hook

Override select_skill_set() to answer from state the agent already has.
3

`default_skill_set`

The manifest’s declared default.
The hook is one method:
Never guess, and never fall back. A name neither the manifest declares raises SkillSetError listing the valid sets — whether it came from --skill-set or from your hook. An agent running with the wrong capability bundle is worse than one that refuses to start, and a hook that returns a set it invented is a wiring bug, not a reason to improvise.

5. Verify what actually loaded

The startup log names the set, the rule that chose it, and every skill that loaded:
And from the CLI:
Switching sets mid-session is one call — the previous set’s skills are unloaded first, so a stale set never lingers in the prompt:

Budget the set

Loading a set injects each body into the system prompt. Three skills of ~250 tokens each is ~750 tokens gone from a window your tool results were already using — enough to overflow a tight context and turn a working run into a context_length_exceeded error. Two habits keep it safe:
  • Keep bodies short. Procedure and judgement calls, not prose. If a skill needs reference material, put it in a file the body links to — resources load only when the agent actually reads them.
  • Subtract the cost where you budget context. If your agent sizes a tool-result envelope against the window, take the loaded set’s cost out of that budget. The email agent’s bulk-triage path does exactly this — and measure the result: its three-skill personal set costs ~1,334 prompt tokens, cutting that envelope from 6,144 to 4,810 (the four-skill work set, to 4,070). Losing a fifth to a third of the room for tool results is why its sets are currently switched off pending an eval.

A worked example in the tree

The email agent is the reference implementation of this pattern. It bundles six instruction-only skills and its manifest carries two sets — personal (triage, newsletter digests, travel itineraries) and work (triage, meeting scheduling, action-item extraction, escalation routing), with inbox-triage in both — plus a selector that keys off the connected mailbox: a personal Microsoft account picks personal, a work/school account picks work. A Gmail mailbox carries no equivalent signal, so its kind is unknown, the selector returns None, and the manifest’s default applies — explicitly, never by assuming a work mailbox is personal.
Read it as a code reference, not as “skills are on by default in a shipped agent”: the email agent’s skill_sets: and default_skill_set: blocks are commented out in gaia-agent.yaml pending an eval that shows the skills help, so on the shipped binary it loads no skills at all. The wiring is what’s worth copying; whether to turn it on is an eval question for your own agent too.