
2025 年,AI Agent 领域出现了两条几乎同时推进的技术线索。一条是 Google 主导的 Agent2Agent(A2A)协议,试图定义智能体之间如何发现、委派和协作;另一条是 Anthropic 提出的 Agent Skills 标准,试图定义智能体的专业能力如何被封装、分发和按需加载。
这两条线索经常被放在一起讨论,却很少被真正拆开来看。一个容易被忽略的事实是:A2A 和 Skills 解决的是不同层次的问题。 A2A 处理的是“协作层”——智能体之间如何谈判、委派和追踪任务状态;Skills 处理的是“能力层”——智能体内部如何组织专业知识、脚本和资源。二者不是替代关系,而是上下咬合的关系。
理解这层咬合,是理解下一代智能体系统架构的关键。
A2A 在 2025 年 4 月由 Google 发布,同年 6 月捐赠给 Linux 基金会,2026 年 3 月 v1.0 冻结,随后迁入 Agentic AI Foundation,与 MCP、AGENTS.md 等标准并列。一个值得注意的判断来自 A2A v1.0 Builder's Guide:剥去营销语言,A2A 实际上只标准化了四个对象——Agent Card、Task、Message 和 Artifact。
这个“四对象”视角很重要,因为它暴露了 A2A 的核心设计约束:对端智能体是不可见的。 你拿不到它的工具列表、记忆、模型或内部计划。你能得到的是一张 Agent Card、一个 Task ID,以及 Task 经过的状态序列。
Agent Card 是一份 JSON 清单,发布在 /.well-known/agent-card.json 路径下,声明智能体的身份、能力、传输绑定和认证方式。其中最关键的部分是 skills 数组——这是智能体向外宣告“我能做什么”的标准化方式。一个 skill 通常包含 id、name、description、tags、examples 以及支持的输入输出模态。
值得仔细品味的是,A2A 的 skill 定义和 MCP 的 tool 定义在抽象层级上并不相同。MCP 的 tool 是具体的 API 操作,而 A2A 的 skill 是更高层的任务类别,通过自然语言来描述,留给对端智能体判断是否委派的空间。这意味着 A2A 的 skill 本质上是“意图声明”,而非“函数签名”。
Task 生命周期是 A2A 协作语义的核心载体。v1.0 定义了一个八状态生命周期,从 submitted 到最终状态,支持阻塞和流式两种模式。流式模式下,客户端通过 SSE 订阅 Task 更新,接收增量生成的 Artifact。这套机制的设计意图很明确:让智能体之间的协作在时间维度上可观察、可恢复、可中断。
如果 A2A 定义了智能体“如何对外说话”,那么 Skill 定义的是智能体“内部如何组织做事的知识”。
Anthropic 在 2025 年 10 月正式提出 Agent Skills,其核心洞察非常朴素:与其为每个用例构建定制智能体,不如让通用智能体通过加载可组合的技能来获得专业能力。Skill 的物理形态极其简单——一个目录,里面有一个 SKILL.md 文件,可选地附带脚本、参考文档和资源。
但简单的外表下藏着一个精心设计的信息架构。SKILL.md 的 YAML frontmatter 包含 name 和 description,这两个字段在智能体启动时被预加载到系统提示中。这是第一层渐进式披露:只给够判断“什么时候该用这个 skill”的信息,而不加载全部内容。
当模型判断 skill 相关时,它读取完整的 SKILL.md。这是第二层。如果 skill 内部引用了额外文件(比如 reference.md 或 forms.md),模型只在需要时读取那些文件。这是第三层及更深层。
渐进式披露之所以关键,是因为它把“技能包能有多大”这个问题从上下文窗口的约束中解放了出来。一个 skill 可以捆绑数百页的参考文档,只要模型不会在每次使用时都全部读取。在 Claude Code 的实践中,skill 被放置在 .claude/skills/<name>/SKILL.md 路径下,目录名成为用户输入的命令。
这个设计有一个容易被忽略但极其重要的后果:Skill 是文件系统原生的。 它不需要任何特殊的运行时或注册机制。一个 skill 就是一个你可以 cat、git clone、zip 上传的目录。这种“无聊”的设计选择,恰恰是它能够跨平台、跨框架传播的前提。
现在来到核心问题:这两个层次如何连接?
第一咬合点:Agent Card 的 skills 数组 = A2A 对 Skill 的发现接口。
当外部智能体通过 A2A 发现一个对端时,它读取 Agent Card 中的 skills 列表。这个列表告诉它:对端能处理哪些任务类别,以及每个任务类别的自然语言描述和示例 prompt。这是 A2A 层面的“能力广告”。
而 Skill 层面提供的是“能力实现”。一个 A2A 智能体对外宣称的 skill,其内部可能由一个或多个 SKILL.md 目录来支撑。但关键在于:A2A 规范并不规定对端如何实现这些 skill。 对端可以用一个 SKILL.md,可以用一组 SKILL.md,也可以用完全不同于 Skill 标准的内部机制。A2A 只关心“你能否处理这个任务”,不关心“你用什么文件结构来处理”。
这种设计的分层是刻意的。它让 A2A 保持了对实现细节的完全不可见——这正是“黑箱交接”的架构含义。
第二咬合点:工具委派(Delegation)让 Skill 可以“借用”对端的工具。
A2A 生态中一个值得关注的开源模式是 delegation 包所实现的“透明工具委派”。问题场景是这样的:一个 A2A 服务端智能体需要访问数据库或搜索 API,但这些能力只有客户端智能体拥有。与其让服务端直接访问这些工具,不如让客户端把工具“借”给服务端。
实现机制是:客户端通过 Agent Card 或消息元数据向服务端广告一组 skills(这些 skills 对应客户端本地的工具)。服务端将这些 skills 转化为代理工具,加入自己的智能体循环中。当服务端的模型调用“search”这个代理工具时,代理工具向客户端发送一个 input required 的 A2A Task,阻塞等待客户端执行本地工具并返回结果。服务端的模型完全不知道这个工具是远程的。
这个模式的意义在于,它让 Skill 的边界和 A2A 的边界可以对齐,也可以不对齐。客户端可以在 Agent Card 中只暴露一部分 skills,服务端也可以只借用其中一部分。二者之间的映射是灵活的。
第三咬合点:ADK 中的 SkillToolset 把 SKILL.md 直接变成 A2A 可发现的能力。
Google 的 Agent Development Kit(ADK)从 1.25.0 版本开始引入 SkillToolset,直接支持加载 SKILL.md 格式的技能包。加载后的技能通过三个层次暴露:L1 是 frontmatter 中的 name 和 description,被注入系统提示;L2 是完整的 SKILL.md body;L3 是捆绑的额外文件。
当一个 ADK 智能体被转换为 A2A 服务端时,AgentSkill 对象被定义在 __main__.py 中,与 AgentCard 一起对外发布。这意味着 Skill 的元数据(name、description)天然成为 A2A 技能发现的信息源,二者之间的信息流是单向且自然的:Skill 是能力的本地表述,Agent Card 是能力的网络表述。
以下代码展示一个最小模式:一个 A2A 服务端智能体,其 Agent Card 中声明的 skill 对应本地加载的 SKILL.md,并通过代理工具机制将对端工具纳入自身能力边界。
# a2a_skill_server.py — A2A 服务端,暴露一个 skill,同时借用客户端工具
import asyncio
import json
from pathlib import Path
from typing import Any
# ============================================================
# 1. Skill 加载层 — 从文件系统加载 SKILL.md
# ============================================================
class SkillLoader:
"""极简 SKILL.md 加载器,模拟渐进式披露的三个层次。"""
def __init__(self, skills_root: str):
self.skills_root = Path(skills_root)
def load_frontmatter(self, skill_name: str) -> dict:
"""L1: 只加载 frontmatter,用于构建 Agent Card 和系统提示。"""
skill_file = self.skills_root / skill_name / "SKILL.md"
text = skill_file.read_text()
# 解析 --- 之间的 YAML frontmatter
_, frontmatter, _ = text.split("---", 2)
meta = {}
for line in frontmatter.strip().split("\n"):
key, _, value = line.partition(":")
meta[key.strip()] = value.strip()
return meta
def load_body(self, skill_name: str) -> str:
"""L2: 加载完整 SKILL.md body(不含 frontmatter)。"""
skill_file = self.skills_root / skill_name / "SKILL.md"
text = skill_file.read_text()
parts = text.split("---", 2)
return parts[2].strip()
# ============================================================
# 2. 代理工具层 — 将客户端 Skills 转化为本地可调用工具
# ============================================================
class DelegatedSkill:
"""一个从客户端借来的 skill,在服务端表现为代理工具。"""
def __init__(self, skill_id: str, description: str,
requester: Any):
self.skill_id = skill_id
self.description = description
self.requester = requester # A2A 客户端句柄
async def invoke(self, params: dict) -> Any:
"""调用代理工具时,向客户端发送 input-required Task 并等待结果。"""
result = await self.requester.request_input(
skill_id=self.skill_id,
params=params,
)
return result
# ============================================================
# 3. A2A 服务端 — 发布 Agent Card,处理传入 Task
# ============================================================
class A2ASkillServer:
def __init__(self, agent_name: str, base_url: str,
skill_loader: SkillLoader,
local_skills: list[str],
delegated_skills: list[DelegatedSkill]):
self.agent_name = agent_name
self.base_url = base_url
self.skill_loader = skill_loader
self.local_skills = local_skills
self.delegated_skills = delegated_skills
def build_agent_card(self) -> dict:
"""从本地 skills 和委派 skills 构建 Agent Card。"""
skills = []
# 本地 skill:从 SKILL.md frontmatter 导出
for name in self.local_skills:
meta = self.skill_loader.load_frontmatter(name)
skills.append({
"id": name,
"name": meta.get("name", name),
"description": meta.get("description", ""),
"tags": meta.get("tags", "").split(",") if
meta.get("tags") else [],
})
# 委派 skill:从客户端广告的 skills 导出
for ds in self.delegated_skills:
skills.append({
"id": ds.skill_id,
"name": ds.skill_id,
"description": ds.description,
})
return {
"name": self.agent_name,
"url": self.base_url,
"version": "1.0.0",
"capabilities": {"streaming": True},
"skills": skills,
}
async def handle_task(self, task: dict) -> dict:
"""处理传入的 A2A Task,路由到本地 skill 或代理 skill。"""
skill_id = task["params"].get("skill_id")
payload = task["params"].get("input", {})
# 1. 检查本地 skill
if skill_id in self.local_skills:
body = self.skill_loader.load_body(skill_id)
# 在实际实现中,这里会将 body 注入模型上下文并执行
return {
"task_id": task["id"],
"state": "completed",
"artifacts": [{
"type": "text",
"content": f"[local skill {skill_id} executed]"
}],
}
# 2. 检查委派 skill
for ds in self.delegated_skills:
if ds.skill_id == skill_id:
result = await ds.invoke(payload)
return {
"task_id": task["id"],
"state": "completed",
"artifacts": [{"type": "text", "content": result}],
}
return {
"task_id": task["id"],
"state": "failed",
"error": f"Unknown skill: {skill_id}",
}这段代码的要点在于 build_agent_card 方法:它将本地 SKILL.md 的 frontmatter 和委派 skills 的声明合并到同一个 skills 数组中。对 A2A 的发现者来说,二者看起来是一样的——都是可委派的能力。但内部路由逻辑清楚地区分了两条路径:本地 skill 直接执行,委派 skill 通过 A2A 反向调用客户端。
A2A + Skills 的组合虽然方向正确,但仍有几处未被充分解决的张力,值得开发者警惕。
张力一:Agent Card 中 skill 的粒度控制。
A2A 的 Agent Card 是一个公开的 JSON 文档。你声明的 skills 越详细,发现者越容易正确路由;但你也暴露了越多的能力拓扑。在安全敏感场景中,Agent Card 的 skills 数组本身就是一个攻击面。目前的协议允许 skill 包含 examples 字段——示例 prompt 如果写得太具体,可能泄露内部工作流的信息。
张力二:渐进式披露在 A2A 委派中的不可见性。
Skills 的渐进式披露是智能体内部的加载策略。当一个 A2A Task 被委派给对端时,委派方完全看不到对端内部用了哪个 SKILL.md、加载了哪些参考文件。这既是优势(黑箱封装),也是风险:如果对端的 skill 实现质量不佳,委派方无法在 Task 执行前进行任何质量评估。A2A 的 Task 生命周期提供了状态可见性,但没有提供能力质量的可见性。
张力三:delegation 模式的安全边界。
代理工具机制让服务端可以“借用”客户端的工具,但这也意味着客户端的工具在服务端的模型上下文中被间接调用。服务端的模型决定何时调用代理工具、传入什么参数。如果服务端被恶意 prompt 注入攻击,代理工具可能被诱导执行超出预期的操作。当前的 delegation 包没有内置的权限细化机制——借用就是全量借用。
张力四:多智能体协调经验的丢失。
Swarm Skills 的研究指出了一个更深层的问题:当前的多智能体协调协议(包括 A2A 的 Task 路由逻辑)存在于框架内部代码或静态配置中,无法作为可分发的资产被复用。每次多智能体会话结束后,协调决策——任务如何分解、角色如何动态实例化、冲突如何解决——随着执行轨迹一起被丢弃。Skills 标准解决了单智能体能力的沉淀问题,但多智能体协调经验的沉淀仍然是一个开放问题。
A2A 和 Skills 之间的关系,最好用一句话概括:A2A 是智能体之间的“HTTP”,Skills 是智能体内部的“文件系统”。 HTTP 不关心你的文件系统怎么组织,文件系统也不需要知道 HTTP 怎么传输。
这种分层之所以健康,是因为它允许每一层独立演进。A2A 可以继续完善 Task 生命周期和流式语义,而不需要触及 skill 的实现细节;Skills 可以继续探索更复杂的渐进式披露结构,而不需要关心对端用什么传输协议来发现它们。
真正的工作,在于理解二者之间的映射层——如何决定哪些内部 skill 应该对外声明,如何设计 Agent Card 中 skill 的粒度和描述,如何在 delegation 场景中划定权限边界。这些不是协议层面的问题,而是工程层面的判断。而恰恰是这些判断,决定了一个多智能体系统是“能跑”还是“可信”。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。