Skills – Composable Agent Packages¶
Skills turn YAML + Markdown into executable agent graphs. Instead of writing Python code for every agent topology, you declare agents and their wiring in a SKILL.md file and load it with Skill("path/"). The same file that documents your agent system for humans and coding agents also runs as a live adk-fluent pipeline.
When Skills Make Life Easy¶
Skills aren’t always the right tool. Here are the five scenarios where they provide a step change — and the anti-patterns where you should stick with Python.
1. Your team has domain experts who aren’t Python developers¶
A product manager, researcher, or domain specialist can author this:
agents:
classifier:
instruct: |
Classify the customer issue into: billing, technical, account.
Be precise — routing depends on your classification.
writes: category
billing_handler:
instruct: Handle billing issues with empathy. Offer refund if appropriate.
technical_handler:
instruct: Diagnose the technical problem step by step.
topology: classifier >> (billing_handler | technical_handler)
They don’t need to know what BuilderBase is, what .reads() vs .writes() means in Python, or how SequentialAgent wiring works. They write instructions and topology in a format they already understand from config files. An engineer reviews the YAML and deploys it.
Without skills, the domain expert writes a requirements doc, an engineer translates it to Python, the domain expert reviews the Python they can’t fully read, and every prompt tweak requires an engineer round-trip.
2. You’re building a library of reusable capabilities¶
You have 15 agent topologies across your organization. Without skills:
# team_a/agents/research.py — 45 lines
# team_b/agents/research.py — 52 lines (slightly different copy)
# team_c/research_utils.py — 38 lines (yet another copy)
With skills:
# One shared skill file, three consumers
research = Skill("shared_skills/research_pipeline/")
# Team A: use as-is
team_a = research.ask("Research quantum computing")
# Team B: stronger model
team_b = research.model("gemini-2.5-pro").ask("Research quantum computing")
# Team C: custom search tool
team_c = research.inject(web_search=internal_search).ask("Research quantum computing")
The skill is the single source of truth. Teams customize at load time with .model(), .inject(), .configure() — without forking the code.
3. You’re composing agent systems from existing pieces¶
This is where the multiplier is largest. You have working skills and want to assemble them into larger systems:
# Without skills: 80+ lines of Python wiring
researcher = Agent("researcher", "gemini-2.5-flash").instruct("...").writes("findings")
fact_checker = Agent("fact_checker", "gemini-2.5-flash").instruct("...").reads("findings").writes("verified")
writer = Agent("writer", "gemini-2.5-pro").instruct("...").reads("verified").writes("draft")
critic = Agent("critic", "gemini-2.5-flash").instruct("...").reads("draft").writes("quality")
editor = Agent("editor", "gemini-2.5-pro").instruct("...").reads("draft")
pipeline = researcher >> fact_checker >> writer >> (writer >> critic) * 3 >> editor
# With skills: 3 lines
pipeline = (
Skill("skills/research_pipeline/")
>> (Skill("skills/writing/") >> Skill("skills/critique/")) * 3
>> Skill("skills/editing/")
)
Each skill is a tested, documented black box. You compose at the skill level — not the agent level.
4. You want the same file to be docs AND runtime¶
A SKILL.md file is simultaneously consumed by:
Consumer |
What happens |
|---|---|
Claude Code / Gemini CLI |
Coding agent reads the prose and learns how to use the skill |
ADK SkillToolset |
Progressive disclosure — LLM loads instructions on demand |
adk-fluent |
Parses |
Humans reading GitHub |
Markdown renders as documentation |
Without skills, you maintain two artifacts: a Python file and a separate README explaining it. They drift apart. With skills, there is one file and it cannot drift from itself.
5. You need rapid prototyping with instant feedback¶
When you’re iterating on prompt design, skills let you edit YAML and re-run without touching Python:
# Edit the YAML
vim skills/research_pipeline/SKILL.md
# Re-run immediately — no code changes
python -c "from adk_fluent import Skill; print(Skill('skills/research_pipeline/').ask('test'))"
Compare this to editing Python files, re-importing modules, dealing with class definitions — skills collapse the edit-run loop.
When NOT to use skills¶
Skills are the wrong choice when:
Scenario |
Use instead |
|---|---|
Complex conditional logic in agent wiring (if/else, dynamic routing beyond |
Python with |
Dynamic tool registration that changes per request |
Python with runtime |
Agents that need deep callback customization (custom |
Python with explicit callback functions |
Single-use, one-off agents that won’t be reused |
Inline |
Performance-critical inner loops where parsing YAML adds overhead |
Pre-built Python agents |
Rule of thumb: If the topology is stable and the main variation is in prompts, models, and tools — use a skill. If the topology itself changes dynamically — use Python.
Architecture¶
SKILL.md File
┌──────────────────────────┐
│ --- │
│ name: research_pipeline │ parse_skill_file()
│ agents: │ ────────────────────────► SkillDefinition
│ researcher: ... │ │
│ writer: ... │ │
│ topology: a >> b │ │
│ --- │ ▼
│ # Documentation │ Skill("path/")
│ Use when user needs... │ │
└──────────────────────────┘ │ .build()
▼
Native ADK Agent
(SequentialAgent, etc.)
Three components:
Component |
Role |
Description |
|---|---|---|
|
Builder |
Load, configure, and compose a skill from a SKILL.md file |
|
Discovery |
Scan directories for skills, find by name/tags |
|
Tool wrapper |
Wrap ADK’s native |
Writing a Skill File¶
A skill file is standard agentskills.io format extended with an agents: block:
---
name: research_pipeline
description: Multi-step research with fact-checking and synthesis.
version: "1.0.0"
tags: [research, synthesis, citations]
agents:
researcher:
model: gemini-2.5-flash
instruct: |
Research {topic} thoroughly.
Find primary sources and extract key findings.
tools: [web_search]
writes: findings
fact_checker:
model: gemini-2.5-flash
instruct: |
Verify the claims in {findings}.
Flag unsupported assertions.
reads: [findings]
writes: verified_findings
synthesizer:
model: gemini-2.5-pro
instruct: |
Synthesize {verified_findings} into a coherent report
with citations.
reads: [verified_findings]
writes: report
topology: researcher >> fact_checker >> synthesizer
input:
topic: str
output:
report: str
---
# Research Pipeline
Use when the user needs comprehensive research with citations.
Skill File Fields¶
Field |
Required |
Description |
|---|---|---|
|
Yes |
Skill identifier (lowercase, underscores OK) |
|
Yes |
What the skill does and when to use it |
|
No |
Semantic version |
|
No |
Discovery tags ( |
|
No |
Agent topology declaration (adk-fluent extension) |
|
No |
Expression wiring agents ( |
|
No |
Input contract (key: type pairs) |
|
No |
Output contract (key: type pairs) |
|
No |
Inline evaluation cases |
|
No |
Arbitrary key-value pairs |
Markdown body |
No |
Documentation for humans and coding agents |
Agent Definition Fields¶
Each agent in the agents: block supports:
Field |
Description |
Example |
|---|---|---|
|
LLM model (default: |
|
|
System prompt with |
|
|
Tool names (resolved via |
|
|
State keys to inject as context |
|
|
State key to store the agent’s output |
|
|
Description for transfer routing |
|
Topology Expressions¶
The topology: field uses the same operators as Python expressions:
Expression |
Meaning |
ADK Result |
|---|---|---|
|
Sequential pipeline |
|
|
Parallel fan-out |
|
|
Loop 3 iterations |
|
|
Fan-out then merge |
Pipeline with nested FanOut |
|
Loop a pipeline |
Loop with nested Pipeline |
If no topology: is specified, agents run in definition order as a pipeline.
Backward Compatibility¶
Skills without an agents: block work everywhere as pure documentation – Claude Code, Gemini CLI, Cursor, and 30+ other tools. The agents: block is an opt-in extension. A skill file can be both documentation and an executable agent graph.
Loading and Running Skills¶
Basic Usage¶
from adk_fluent import Skill
# Load from directory (looks for SKILL.md inside)
skill = Skill("skills/research_pipeline/")
# Load from file path directly
skill = Skill("skills/research_pipeline/SKILL.md")
# Build into native ADK agent
agent = skill.build()
# One-shot execution
result = skill.ask("Research quantum computing advances")
# Async execution
result = await skill.ask_async("Research quantum computing")
Model Override¶
Override the model for all agents in the skill:
# Use a stronger model for the entire skill
fast = Skill("skills/research_pipeline/").model("gemini-2.5-flash")
pro = Skill("skills/research_pipeline/").model("gemini-2.5-pro")
Per-Agent Configuration¶
Override settings for a specific agent:
skill = (
Skill("skills/research_pipeline/")
.configure("synthesizer", model="gemini-2.5-pro")
.configure("researcher", model="gemini-2.5-flash")
)
Tool Injection¶
Tools referenced by name in agents.tools are resolved via .inject():
def my_search(query: str) -> str:
"""Custom web search implementation."""
return requests.get(f"https://api.search.com/?q={query}").text
skill = Skill("skills/research_pipeline/").inject(web_search=my_search)
Resolution order:
.inject()resources (explicit)Built-in ADK tools (
google_search)Skipped (fails at runtime if the agent needs it)
Composing Skills¶
Skills extend BuilderBase – all operators work exactly like agents:
Pipeline (>>)¶
research = Skill("skills/research_pipeline/")
writing = Skill("skills/technical_writing/")
review = Skill("skills/code_review/")
pipeline = research >> writing >> review
result = pipeline.ask("Write a blog about RAG architectures")
Parallel (|)¶
web = Skill("skills/web_research/")
papers = Skill("skills/paper_research/")
parallel = web | papers
# Both run concurrently, results merged into state
Loop (*)¶
writer = Skill("skills/writing/")
critic = Skill("skills/critique/")
from adk_fluent import until
refined = (writer >> critic) * until(lambda s: s.get("quality") == "APPROVED", max=3)
Fallback (//)¶
fast = Skill("skills/quick_research/")
deep = Skill("skills/deep_research/")
research = fast // deep # Try fast first, fall back to deep
Skill + Agent Mixing¶
from adk_fluent import Agent, Route
research = Skill("skills/research_pipeline/")
pipeline = (
Agent("classifier", "gemini-2.5-flash")
.instruct("Classify the query into: research, coding, general.")
.writes("category")
>> Route("category")
.eq("research", research)
.eq("coding", Skill("skills/code_gen/"))
.otherwise(Agent("general", "gemini-2.5-flash").instruct("Help the user."))
)
Skill as AgentTool¶
The coordinator LLM decides when to invoke each skill:
coordinator = (
Agent("coordinator", "gemini-2.5-pro")
.instruct("You have specialized skills. Use them as needed.")
.agent_tool(Skill("skills/research_pipeline/").describe("Deep research"))
.agent_tool(Skill("skills/code_review/").describe("Code review"))
.agent_tool(Skill("skills/data_analysis/").describe("Data analysis"))
)
Skill Discovery¶
SkillRegistry¶
Scan a directory tree for skills and discover them by name or tags:
from adk_fluent import SkillRegistry
registry = SkillRegistry("skills/")
# List all skills
for info in registry.list():
print(f"{info['name']}: {info['description']} (tags: {info['tags']})")
# Find by tags
research_skills = registry.find(tags=["research"])
# Find by name substring
code_skills = registry.find(name="code")
# Get a specific skill by exact name
skill = registry.get("research_pipeline")
# Check if a skill exists
if "research_pipeline" in registry:
print("Found it!")
Registry API¶
Method |
Description |
|---|---|
|
Scan directory recursively for SKILL.md files |
|
All skills with metadata (name, description, tags, path) |
|
Filter by tags (all must match) or name substring |
|
Get Skill builder by exact name (raises |
|
Sorted list of all skill names |
|
Number of discovered skills |
|
Check if a skill exists |
ADK Native SkillToolset (T.skill())¶
For ADK’s native progressive disclosure pattern (L1/L2/L3), use T.skill():
from adk_fluent import Agent, T
agent = (
Agent("assistant", "gemini-2.5-pro")
.instruct("Help the user. Load skills as needed.")
.tools(T.skill("skills/research_pipeline/"))
)
This wraps ADK’s SkillToolset which provides:
L1 – Metadata (~100 tokens): Always in system prompt as XML index
L2 – Instructions: Loaded on demand via
load_skill()tool callL3 – Resources: Loaded via
load_skill_resource()on demand
The LLM decides when to activate a skill – its tools become visible only while the skill is active.
T.skill() vs Skill()¶
|
|
|
|---|---|---|
Purpose |
Native ADK progressive disclosure |
Fluent builder for agent graphs |
Mechanism |
LLM loads skill instructions on demand |
Parses |
Requires |
ADK |
Only adk-fluent |
Best for |
Many skills, LLM picks which to use |
Predetermined topology, compose with operators |
Introspection¶
skill = Skill("skills/research_pipeline/")
# View the topology expression
skill.topology_expr()
# → "researcher >> fact_checker >> synthesizer"
# View the input/output contract
skill.contract()
# → {"input": {"topic": "str"}, "output": {"report": "str"}}
# Print configuration summary (inherited from BuilderBase)
skill.explain()
# Returns a multi-line summary of builder state
Skill Builder API Reference¶
Method |
Description |
|---|---|
|
Load and parse a SKILL.md file |
|
Override model for ALL agents in the skill |
|
Inject tool implementations by name |
|
Override settings for a specific agent |
|
Set/override skill description |
|
Produce native ADK agent graph |
|
One-shot sync execution |
|
One-shot async execution |
|
Async streaming |
|
Multi-turn chat |
|
Return the topology expression string |
|
Return input/output schema |
|
Print configuration summary |
|
Replace LLM with canned responses |
|
Inline smoke test |
All operators: >> (pipeline), | (parallel), * (loop), // (fallback), @ (schema)
Best Practices¶
Use valid Python identifiers for skill names – ADK requires agent names without hyphens. Use underscores:
research_pipeline, notresearch-pipeline.Declare
topology:explicitly – don’t rely on definition order. Explicit wiring is clearer and supports fan-out/loop patterns.Use
.inject()for external tools – keep skills self-contained. Tool implementations are injected at load time, not baked into the YAML.Use
.configure()sparingly – if you’re overriding every agent, you’re fighting the skill. Write a new one instead.Keep skills focused – one skill = one capability. Compose multiple skills with
>>and|rather than making one giant skill.Write the markdown body – the prose below
---serves as documentation for coding agents and humans. A skill without documentation is just configuration.Add
eval:cases – inline evaluation cases document expected behavior and can be used withE.suite()for automated testing.Use
tags:for discovery –SkillRegistry.find(tags=["research"])only works if skills are tagged consistently.
Cookbook Examples¶
See Example 77 – Skill-Based Agents for 12 runnable patterns covering all the concepts above.
Example skill files:
examples/skills/research_pipeline/SKILL.md– Sequential pipelineexamples/skills/code_reviewer/SKILL.md– Parallel fan-out with merge