What people mean by an ADK , '’': skill’
Start by naming the thing honestly. ADK does not ship a first-class Skill abstraction the way it ships Agent, Runner, Session, or a tool declaration. What it ships instead are composition primitives powerful enough that a ‘skill’ needs no dedicated type: a tool is just a typed function, an agent can hold a list of tools and a list of sub-agents, and any agent can be wrapped as a tool for another agent. A skill, in the way practitioners use the word, is the unit of capability reuse you assemble from those primitives.
Concretely, a skill is a bundle with three parts, the third optional: a set of tools that naturally belong together, an instruction fragment that teaches the model when and how to use them, and — when the capability is complex enough to deserve its own reasoning loop — a sub-agent that owns all of it behind one boundary. ‘Refunds,’ ‘itinerary planning,’ ‘invoice reconciliation’ are skills in this sense. Treating the pattern as a pattern, rather than pretending it is a framework class, is what keeps you accurate: you get the reuse benefits by disciplined packaging, not by inheriting from something ADK does not provide.
Three altitudes of reuse: tool, skill, agent
The clearest way to place ‘skill’ is on a granularity ladder between the two things ADK does formalize. A tool is the smallest unit — one function, one declaration the model reads. A full agent is the largest — an independently deployable reasoning loop with its own model, instruction, and catalog. A skill sits in between: bigger than one tool, smaller than a whole product.
| Altitude | Unit | Reuse mechanism | Owns a reasoning loop? |
|---|---|---|---|
| Tool | One typed function / one MCP or OpenAPI operation | Import the function; list it in tools=[] | No — the parent model decides |
| Skill | Cohesive tools + instruction (+ optional sub-agent) | Factory returns the bundle or the sub-agent | Optional — only if wrapped as a sub-agent |
| Agent | A complete, independently runnable agent | Deploy and expose (in-process, or via A2A) | Yes — its own loop and lifecycle |
The ladder matters because each rung has a different cost. A tool is free to add until the catalog grows past the model’s routing accuracy knee. A skill costs a boundary you must design. A full agent costs a deployment and a contract. Picking the right rung for a given capability is the core judgement this article is about.
Anatomy of a reusable capability bundle
Unpack the three parts, because each solves a different problem. The tools are the hands — the actual functions that read and change the world, each with the declaration discipline the tools article insisted on: descriptions that say when to use it, schemas that constrain arguments, error payloads the model can act on. Grouping them is the first act of skill design: which functions are so frequently used together, and so meaningless apart, that they form one capability?
The instruction fragment is the knowledge the tools cannot carry. A tool declaration tells the model ‘I can issue a refund’; the skill’s instruction tells it ‘always check policy first, never exceed the order total, explain the outcome in one sentence.’ That procedural glue is what turns a pile of callable functions into a competent behaviour. The optional sub-agent is the escape hatch for when the capability needs its own context window, its own narrower tool catalog, or its own multi-step reasoning that would clutter the parent. A simple skill is just tools + instruction text merged into a host agent; a rich skill is a whole sub-agent. The pattern scales precisely because you can start at the cheap end and promote later without changing the callers much.
Composing capabilities into an agent
Here is the whole pattern in code — a refunds capability packaged as a factory, then reused in an orchestrator two different ways. Note what is real ADK (Agent, sub_agents, AgentTool) and what is pure convention (the build_refunds_agent factory and the module boundary):
# refunds_skill.py — a REUSABLE CAPABILITY, packaged as an ordinary
# Python factory. There is no `Skill` type; the ‘skill’ is the
# convention: a bundle of cohesive tools + a focused instruction,
# optionally wrapped as a sub-agent, returned ready to compose.
from google.adk.agents import Agent
from google.adk.tools.agent_tool import AgentTool
# --- the tools that belong together (see the tools article) ---
def lookup_order(order_id: str) -> dict:
"""Fetch an order by its 8-digit numeric ID."""
...
def issue_refund(order_id: str, amount_cents: int, reason: str) -> dict:
"""Refund up to the order total. Amount is in cents."""
...
def get_refund_policy(sku: str) -> dict:
"""Return the refund window and exceptions for a product."""
...
REFUNDS_INSTRUCTION = (
"You handle refunds. Always check get_refund_policy before issuing one; "
"never exceed the order total; explain the outcome in one sentence."
)
def build_refunds_agent(model: str = "gemini-2.0-flash") -> Agent:
"""Package the refunds capability as a self-contained sub-agent."""
return Agent(
name="refunds",
model=model,
description="Processes refunds within policy for a given order.",
instruction=REFUNDS_INSTRUCTION,
tools=[lookup_order, issue_refund, get_refund_policy],
)
# --- reuse it in ANY orchestrator, two different ways ---
refunds = build_refunds_agent()
root = Agent(
name="support",
model="gemini-2.0-flash",
instruction="Route the user to the right capability.",
# (1) as a delegable sub-agent: control TRANSFERS to it
sub_agents=[refunds],
# (2) as a callable tool: it answers and control RETURNS here
tools=[AgentTool(agent=build_refunds_agent())],
)
Nothing here is a special ‘skill’ API. The reuse comes from ordinary software engineering: a function that returns a configured object, imported wherever the capability is needed. The two composition modes on the last lines — sub_agents versus AgentTool — are the real choice you make each time you wire a skill in, and they behave differently enough to deserve their own section.
Sub-agent transfer vs agent-as-a-tool
ADK gives you two genuinely different ways to plug a capability-owning sub-agent into a parent, and confusing them is a common bug. Listing an agent in sub_agents=[...] makes it a transfer target: the parent can hand control to it, and the conversation continues inside the sub-agent until it transfers back. Wrapping the same agent in AgentTool(agent=...) makes it a callable tool: the parent invokes it like any function, the sub-agent produces an answer, and control returns to the parent to continue reasoning.
The rule of thumb: use transfer when the skill should take over the conversation — a refunds specialist that will drive several turns with the user. Use agent-as-a-tool when the skill is a subroutine whose result the parent needs to keep composing with — ‘summarise this document’ feeding into a larger report. Agent-as-a-tool is also the cleaner reuse primitive because it encapsulates the skill completely: the parent sees one tool with one description and never inherits the sub-agent’s internal tools into its own routing budget. That encapsulation is exactly why AgentTool is the workhorse of skill reuse.
When to promote tools into a skill or sub-agent
Every capability starts life as loose tools on one agent. The design question is when to lift a cluster of them into a named bundle, and when to go further and give that bundle its own sub-agent. Promote when the signals line up:
| Signal | Keep as loose tools | Promote to a skill / sub-agent |
|---|---|---|
| Reuse count | Used by one agent only | Wanted by two or more agents |
| Cohesion | Unrelated, used independently | Always used together, meaningless apart |
| Instruction weight | A line or two of guidance | A whole paragraph of when/how rules |
| Routing budget | Catalog still small | Catalog past the selection-accuracy knee |
| Reasoning depth | One call, no follow-up | Multi-step logic worth its own loop |
The routing-budget row is the one teams underweight. Selection accuracy degrades as a single agent’s tool count climbs; folding six related tools behind one AgentTool replaces six entries in the parent’s catalog with one, and moves the fine-grained selection into the sub-agent where the catalog is small and the instruction is focused. Promotion is therefore not just tidiness — it is a measurable lever on routing quality. The counter-force is that every promotion adds a boundary to design and a hop to trace, so promote on evidence, not on instinct.
Packaging: factories, modules, and no hidden state
Because a skill is a convention, the packaging discipline is what makes it reusable rather than copy-pasteable. The load-bearing habit is the factory function: a build_x_agent(...) or x_tools() that returns a fresh bundle each call and takes the things that legitimately vary — the model name, a base URL, a feature flag — as parameters. Returning fresh objects avoids the subtle bug of two orchestrators sharing one mutable agent instance and stepping on each other’s configuration.
Give each skill a real module boundary: one package, an explicit public surface (the factory and maybe the raw tool functions), and everything else private. Keep the skill’s dependencies inside that package so a consumer imports one thing and gets a working capability, not a scavenger hunt for three helper modules. Resist reaching into global state or reading configuration from the ambient environment inside the bundle; pass it in, so the same skill behaves identically in every host. These are unglamorous software-engineering rules, but they are the entire difference between a skill a teammate can adopt in one line and a tangle only its author can wire up.
Versioning a capability across a team
The moment a skill is used by more than one agent, it has consumers, and consumers mean a contract. The contract of a skill is broader than a single function signature: it is the set of tool names and argument schemas the model has learned to call, the sub-agent’s description (which parents route on), and the behavioural promises in its instruction. Change any of those and you can silently break a caller whose prompt or evals depended on the old shape.
Treat the bundle like a small library and apply the same evolution rules. Additive changes — a new tool, a new optional argument, a clarified description — are safe and can ship freely. Breaking changes — renaming a tool, tightening a schema, removing a capability, or materially changing what the instruction promises — deserve a version bump and a migration window, exactly as an API would. Pin skills with an explicit version in your dependency metadata so an upstream refactor cannot redefine an agent’s behaviour on a Friday afternoon. And back the contract with evals owned by the skill: a small suite covering each tool’s happy path and its two most likely failures, run in the skill’s CI so every consumer inherits that coverage instead of re-testing the capability themselves.
Reuse across projects: shared library vs A2A service
There are two fundamentally different distribution channels for a skill, and choosing between them is an architecture decision, not a packaging one. The first is the shared-code channel: the skill is a Python package on your internal index, and every consuming agent imports the factory and runs the capability in its own process. This is the right default within a bounded context — one team, one deployment cadence — because it is simple, fast (no network hop), and easy to trace end to end.
The second is the service channel: the skill is promoted all the way to a standalone agent, deployed on its own, and consumed over the wire. Reach for it when the capability needs to scale independently, is owned by a different team with its own release rhythm, or must be shared across organizational boundaries where you cannot ship each other code at all. The trade is the microservices trade replayed at the agent altitude: you gain autonomy and independent scaling, you pay in network seams, contract management, and a harder failure model. The useful heuristic mirrors the one in the interop article — compose in-process within a team, promote to a service across teams — and it leads directly to how the wider world discovers your capability: the A2A skill.