Skills
Source example: agentflow/examples/skills/graph.py
What you will build
A graph where one assistant can switch into specialized modes at runtime by loading SKILL.md files from disk.
In this tutorial the agent can:
- answer normal questions directly
- call a regular Python tool like
get_weather - call the auto-injected
set_skilltool when a request matches a skill - return to the main loop after the skill content has been loaded
Prerequisites
- Python 3.11 or later
10xscale-agentflowinstalledpython-dotenvinstalled- a model key for the provider used by the example
Install the basics:
pip install 10xscale-agentflow python-dotenv
How the skills system works
The key idea is simple:
- you keep reusable instructions in
SKILL.mdfiles SkillConfigmakes those skills discoverable to the agent- AgentFlow injects a
set_skilltool automatically - when the model decides a skill fits, it calls
set_skill("skill-name") - the skill content comes back as a tool result and becomes part of the next model turn
Step 1 - Create a skills directory
The example stores skills next to the graph file:
agentflow/examples/skills/
├── graph.py
├── chat.py
└── skills/
├── code-review/
│ └── SKILL.md
├── data-analysis/
│ └── SKILL.md
├── humanizer/
│ └── SKILL.md
└── writing-assistant/
└── SKILL.md
Each skill is just a Markdown file with YAML frontmatter.
Example shape:
---
name: code-review
description: Perform thorough code reviews
metadata:
triggers:
- review my code
- find bugs
tags:
- engineering
priority: 10
---
You are now in CODE REVIEW mode.
The frontmatter gives the runtime enough structure to:
- identify the skill
- expose it to the model
- decide which trigger phrases should activate it
- order multiple matching skills by priority
Step 2 - Point SkillConfig at the directory
The example builds the path like this:
from pathlib import Path
from agentflow.core.skills import SkillConfig
SKILLS_DIR = str(Path(__file__).parent / "skills")
Then it passes that into the agent:
agent = Agent(
model="google/gemini-2.5-flash",
system_prompt=[...],
tool_node=ToolNode([get_weather]),
skills=SkillConfig(
skills_dir=SKILLS_DIR,
inject_trigger_table=True,
hot_reload=True,
),
trim_context=True,
)
What these options do:
| Field | Effect |
|---|---|
skills_dir | Tells AgentFlow where to find SKILL.md files |
inject_trigger_table=True | Adds a summary of available skills and triggers to the prompt |
hot_reload=True | Re-reads skill files on each call so edits are picked up immediately |
hot_reload=True is especially useful while authoring skills because you can edit a file and retry without restarting the process.
Step 3 - Combine skills with normal tools
This example is useful because it shows that skills do not replace regular tools.
The graph still exposes a standard weather function:
def get_weather(location: str) -> str:
weather_data = {
"london": "Cloudy, 15°C",
"new york": "Sunny, 22°C",
"tokyo": "Rainy, 18°C",
"paris": "Partly cloudy, 17°C",
}
...
Then the agent is created with that tool node:
tool_node = ToolNode([get_weather])
When skills are enabled, AgentFlow augments that tool node by injecting set_skill into it. The final tool node therefore contains both kinds of capability:
- hand-written Python tools
- the automatically generated
set_skillloader
The example makes that explicit:
tool_node = agent.get_tool_node()
That is the important call. It returns the final tool node after skill tooling has been attached.
Step 4 - Route between the agent and tools
The tutorial uses a standard ReAct loop:
def should_use_tools(state: AgentState) -> str:
if not state.context:
return END
last = state.context[-1]
if last.role == "assistant" and hasattr(last, "tools_calls") and last.tools_calls:
return "TOOL"
if last.role == "tool":
return "MAIN"
return END
Execution flow:
The same loop also handles regular tools. If the user asks for weather, the agent can call get_weather instead of set_skill.
Step 5 - Understand what the model actually sees
With inject_trigger_table=True, the model gets a compact map of skills in the prompt. That helps it decide whether a request like:
review this codeanalyse this datahumanize this textwrite an apology email
should trigger a skill.
When the skill is loaded, the tool returns the full markdown instructions. That means the next assistant turn is grounded in the exact contents of the relevant SKILL.md file.
A useful mental model is:
- the trigger table helps the model choose
set_skilldelivers the full instructions- the next assistant step applies those instructions
Step 6 - Compile and run the graph
The example graph is a classic two-node setup:
graph = StateGraph(
context_manager=MessageContextManager(max_messages=20),
)
graph.add_node("MAIN", agent)
graph.add_node("TOOL", tool_node)
graph.add_conditional_edges(
"MAIN",
should_use_tools,
{"TOOL": "TOOL", END: END},
)
graph.add_edge("TOOL", "MAIN")
graph.set_entry_point("MAIN")
app = graph.compile()
Run it:
cd agentflow/examples/skills
python graph.py
Or pass a query directly:
python graph.py "Review this Python code: def add(a,b): return a+b"
python graph.py "Help me write a professional apology email to a client"
python graph.py "Analyse this data: sales=[120,95,140,88,160] by month"
python graph.py "What's the weather in Tokyo?"
What to verify
When the example starts, it prints the registered tools. You should see:
set_skillget_weather
Then test these scenarios:
| Input | Expected behavior |
|---|---|
| code review request | agent loads code-review skill |
| writing request | agent loads writing-assistant skill |
| humanization request | agent loads humanizer skill |
| weather request | agent uses get_weather instead of a skill |
Why this pattern works well
This design keeps responsibilities separate:
- graph code handles orchestration
- Python tools handle deterministic actions
SKILL.mdfiles hold specialized writing and reasoning instructions
That separation is valuable because non-engineers can often improve a skill file without touching graph wiring.
Common mistakes
- Registering the original
ToolNodeinstead ofagent.get_tool_node(). That drops the injectedset_skilltool. - Putting all domain instructions in the base system prompt instead of splitting them into focused skills.
- Forgetting that skill selection is still model-driven. Good trigger phrases matter.
- Leaving
hot_reload=Truein a production environment where you want more predictable file loading behavior.
Skills architecture recap
Related docs
What you learned
- How AgentFlow discovers
SKILL.mdfiles. - How
SkillConfiginjects aset_skilltool into the tool node. - How to combine skill loading with normal Python tools in one graph.
Next step
→ Continue with Skills Chat to turn the same pattern into a persistent interactive REPL.