When ADK developers say ‘skill,’ they are usually not naming a class — there is no Skill type you import and subclass. They are naming a reuse pattern: a coherent bundle of cohesive tools, a focused instruction that says when and how to use them, and — optionally — a sub-agent that owns the whole capability, packaged so it can be dropped into many agents and shared across a team. It is the altitude one level above an individual tool and one level below a full standalone application. The same word has a second, precise meaning at the network boundary: in the A2A protocol, an agent advertises named skills in its agent card so remote orchestrators can discover and route to what it can do. This piece treats both senses as one idea seen from two sides — the internal composition pattern you build, and the external capability you advertise — and keeps carefully clear about which parts of ADK are real machinery (Agent, sub_agents, AgentTool) and which parts are convention you impose yourself. Where the tools article covered the mechanics of a single tool, this one is about assembling tools into reusable capabilities.

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.

Advertisement

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.

AltitudeUnitReuse mechanismOwns a reasoning loop?
ToolOne typed function / one MCP or OpenAPI operationImport the function; list it in tools=[]No — the parent model decides
SkillCohesive tools + instruction (+ optional sub-agent)Factory returns the bundle or the sub-agentOptional — only if wrapped as a sub-agent
AgentA complete, independently runnable agentDeploy 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:

SignalKeep as loose toolsPromote to a skill / sub-agent
Reuse countUsed by one agent onlyWanted by two or more agents
CohesionUnrelated, used independentlyAlways used together, meaningless apart
Instruction weightA line or two of guidanceA whole paragraph of when/how rules
Routing budgetCatalog still smallCatalog past the selection-accuracy knee
Reasoning depthOne call, no follow-upMulti-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.

Advertisement

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.

The A2A bridge: your bundle becomes an advertised skill

Everything above is the internal face of a skill. The A2A protocol is its external face, and the vocabulary is not a coincidence. When you expose an ADK agent through an A2A server, it serves an agent card at a well-known URL that declares its identity, endpoint, auth schemes, and a list of named skills — each a capability with a description that remote orchestrators’ models read to decide whether and how to delegate. The internal reuse bundle you built is what you advertise there: the refunds sub-agent, described once for your own tree, becomes a refunds skill on the card that a partner’s agent can discover and route to with no knowledge of your code.

This is why the two senses of the word are one idea. The advice for writing an A2A skill description is identical to the advice for a tool or sub-agent description — write it like tool docs: say what the capability does, when to use it, and what it needs, in the routing model’s terms. A remote LLM selects an A2A skill exactly as a local model selects a tool; the only change is that the boundary is now a network and an untrusted counterparty. So the skill you designed for internal reuse — cohesive, well-described, versioned — is already the right shape to advertise; A2A just gives it a card, an auth scheme, and a URL. (The card mechanics, task mapping, and trust tiers are the interop article’s subject; here the point is only that the capability crosses the boundary unchanged in spirit.)

Anti-patterns and gotchas

The pattern has predictable failure modes. The first is the god-skill: a bundle that accretes every loosely related tool until its instruction is a wall of text and its description could match half the user’s requests — routing becomes a coin flip. Skills should be cohesive and narrow; if you cannot describe one in a sentence, it is two skills. The mirror failure is premature promotion: wrapping two tools used by one agent into a sub-agent ‘for reuse’ that never comes, buying a boundary and a hop for no benefit.

Watch three more. Description drift — the sub-agent’s description is the contract the parent routes on, so a description that no longer matches what the skill does produces confident mis-routing that no amount of parent-prompt tuning fixes. Catalog leakage — adding a rich skill via sub_agents when you wanted encapsulation can expose transfer paths you did not intend; reach for AgentTool when the skill should stay a sealed subroutine. And shared mutable instances — building one agent object and importing it everywhere, so a config tweak for one consumer silently changes another; the factory-returns-fresh rule exists precisely to prevent this. None of these are exotic; they are the ordinary hazards of reuse, and they are why the discipline matters more than any API would.

A decision framework

Strip it to the questions you actually ask when a capability appears in front of you:

QuestionWhat the answer tells you
Is it one function, or several that belong together?One → a tool. Several cohesive → a skill.
Does it need its own instruction and reasoning?Yes → wrap it as a sub-agent, not loose tools.
Should it take over the conversation, or return a result?Take over → sub_agents. Return → AgentTool.
Do two or more agents want it?Yes → package a factory in a versioned module.
Is it owned by another team or org?Yes → promote to a standalone agent behind A2A.

Read top to bottom, the table is the whole granularity ladder: a capability climbs from tool, to in-agent skill, to encapsulated sub-agent, to shared library, to advertised A2A service — and it should climb only as far as real reuse and real ownership push it. Most capabilities belong on the lower rungs, and that is correct, not lazy. The skill pattern is valuable exactly because it lets a capability move up the ladder incrementally — the same cohesive bundle, described the same way, wearing a bigger boundary each time — without a rewrite at any step.

An ADK ‘skill’ is not a class you import — it is a reuse and composition pattern: cohesive tools, a focused instruction, and optionally a sub-agent, packaged behind a factory so many agents can share one capability. It lives on a granularity ladder between the individual tool (the tools article’s subject) and the full standalone agent. Compose it with real ADK machinery — sub_agents to hand over control, AgentTool to call it as a sealed subroutine — and promote a cluster of tools into a skill only when reuse, cohesion, instruction weight, or routing budget justify the boundary. Version it like a library, distribute it as shared code within a team and as an A2A service across teams, and remember that the capability you designed for internal reuse is exactly what you advertise as a named skill in the A2A agent card — one idea, seen from inside and out.