ExAgent. Skills
(ExAgent v2.2.0)
View Source
Skills that the model loads on demand (progressive disclosure).
An agent with many specialised procedures should not send all of them on every request. With skills, the model sees a short catalog (name + description) and loads a skill's full instructions only when a task needs it:
skills = ExAgent.Skills.from_dir!(Application.app_dir(:my_app, "priv/skills"))
agent = ExAgent.new(model: model, instructions: "You are...", skills: skills)ExAgent.new/1 with :skills adds:
- a
load_skilltool whose description lists the catalog. Its result (the skill instructions, plus its file list and newly available tools) becomes an ordinary tool return in the history, so a loaded skill stays in context across steps,ExAgent.Serverturns, snapshots and continuations; - a
read_skill_filetool, when a skill has a directory, to read files such asreferences/forms.mdrelative to that directory, never outside it; - every skill's
:toolsand, when there are any, anExAgent.Skills.Gatebefore the agent's capabilities that offers them only once their skill has been loaded; ExAgent.Skills.Restoreafter the agent's capabilities, which restores loaded skills that a compaction capability removed from the request.
Keeping the catalog in the tool definition leaves :instructions and the
history unchanged, and lets providers cache it with the other tool definitions.
Changing the skill set changes that definition, like changing any tool. Without
:skills, agents are unaffected.
Directory layout
priv/skills/
release-notes/
SKILL.md
references/style.md
incident-report/
SKILL.mdA SKILL.md starts with YAML frontmatter:
---
name: release-notes
description: Write release notes. Use when asked to summarise changes for a release.
---
# Release notes
1. Read references/style.md ...This is the format used by other agent harnesses, so the same skills work
there. See ExAgent.Skill for fields and validation rules.
Tools gated by a skill
skills = ExAgent.Skills.from_dir!(dir, tools: %{"deploy" => DeployTools.tools()})The deploy tools are omitted from requests until deploy is loaded. They stay
in the agent inventory, so persisted continuations restore them as usual. A
tool listed both in :tools and in a skill is gated. The gate controls what
the model is offered; it is not an authority boundary.
Composing by hand
tools/2 and capabilities/1 return the pieces ExAgent.new/1 adds, for
example to place a capability that measures the final request after
:restore. Keep :gate before capabilities that restrict function_tools
(so they keep the final say) and :restore after any compaction capability.
Skills only add instructions and tools; they do not grant permissions or execute skill scripts.
Summary
Functions
Build the capabilities added for skills
Read every immediate subdirectory of root that contains a SKILL.md.
Like from_dir/2, raising ArgumentError on error.
Names of the skills successfully loaded in messages, each once, ordered by
its most recent load.
Build the tools added for skills: load_skill, read_skill_file when any
skill has a :path, then each skill's gated tools (deduplicated).
Validate a skill list for an agent: valid skills, unique names, and gated tools whose names are unique.
Functions
@spec capabilities([ExAgent.Skill.t()]) :: %{ gate: ExAgent.Skills.Gate.t() | nil, restore: module() }
Build the capabilities added for skills:
:gate— anExAgent.Skills.Gate, ornilwhen no skill has tools. Place it before capabilities that restrictfunction_tools.:restore—ExAgent.Skills.Restore. Place it after compaction.
@spec from_dir(Path.t(), keyword()) :: {:ok, [ExAgent.Skill.t()]} | {:error, term()}
Read every immediate subdirectory of root that contains a SKILL.md.
Skills are returned sorted by name. Directories without SKILL.md and hidden
directories are ignored. An invalid skill, a duplicate name or an unreadable
root fails the whole call.
Options:
:tools— map of skill name to its gatedExAgent.Tools.
@spec from_dir!(Path.t(), keyword()) :: [ExAgent.Skill.t()]
Like from_dir/2, raising ArgumentError on error.
@spec loaded([ExAgent.Message.t()]) :: [String.t()]
Names of the skills successfully loaded in messages, each once, ordered by
its most recent load.
Useful to show which skills a conversation used, for example
ExAgent.Skills.loaded(ExAgent.Server.history(server)).
@spec tools([ExAgent.Skill.t()], keyword()) :: [ExAgent.Tool.t()]
Build the tools added for skills: load_skill, read_skill_file when any
skill has a :path, then each skill's gated tools (deduplicated).
Options: :max_file_bytes (default 256 KiB) bounds both the skill
instructions returned by load_skill and files read by read_skill_file.
@spec validate([ExAgent.Skill.t()]) :: :ok | {:error, term()}
Validate a skill list for an agent: valid skills, unique names, and gated tools whose names are unique.