HarmonyOS 7 新特性(三)|端侧 A2A:从 Agent Card 到任务状态管理

本文基于 HarmonyOS 7(API 26)Developer Beta 阶段公开资料,讨论通过 Agent Framework Kit、
AgentAbilityExtension实现端侧 A2A 服务的工程方法。文中的协议对象是便于理解的简化模型,具体字段和接口签名请以当前 SDK 文档为准。
端侧 A2A(Agent to Agent)最吸引人的演示,是一个智能体自动找到另一个智能体并完成任务。但在真实项目里,“调用成功一次”只是起点:能力如何发现、调用方是否可信、长任务如何恢复、重复请求如何处理、用户取消后谁负责收尾,这些才决定它能否上线。
本文用一个旅行场景串起完整链路:行程助手需要调用天气智能体与日历智能体,生成建议后再由用户决定是否写入日程。重点不是某个接口,而是 Agent Card、任务状态机、权限和幂等如何形成闭环。
一、先把协作拆成三段,而不是一条“魔法链路”
一次可靠 A2A 协作至少分为三段:
- 发现:读取 Agent Card,判断对方是否真的提供所需技能;
- 执行:发送结构化任务,并持续处理运行、完成、失败和取消;
- 落地:把结果交给本地领域服务,必要时再次确认后产生副作用。
天气查询是只读动作,写入日历是可逆写入。两者不能因为都发生在一次旅行规划中,就共享同一套授权策略。
| 子任务 | 数据 | 副作用 | 推荐策略 |
|---|---|---|---|
| 查询目的地天气 | 城市、日期 | 无 | 最小授权,可自动执行 |
| 生成行程建议 | 偏好、时间范围 | 无 | 数据最小化,结果可解释 |
| 写入系统日历 | 标题、地点、时间 | 创建事件 | 展示变更摘要后确认 |
| 向同行人发消息 | 联系人、内容 | 对外发送 | 必须明确确认 |

二、Agent Card 是版本化契约
官方将 Agent Card 描述为 JSON 元数据文档,用于表达智能体身份、能力、端点、技能与认证要求。它不是产品宣传页,而是客户端决定“能不能调用、应该怎样调用”的依据。
下面是简化示例,字段以当前官方规范为准:
{
"name": "weather-agent",
"version": "2.1.0",
"endpoint": "local://weather-agent",
"authentication": ["user-consent"],
"skills": [
{
"id": "query-forecast",
"description": "查询指定城市和日期范围的天气预报",
"input": ["cityCode", "startDate", "endDate"],
"output": ["dailyForecast", "sourceTime"]
}
]
}
客户端至少要校验 Agent 身份、Card 版本、目标技能、认证方式和字段兼容性。若服务端删除字段、改变单位或把只读能力改成写操作,应视为契约变化,不能静默升级。
三、不要把 Card 描述直接变成执行权限
“对方声明自己会做什么”与“当前用户允许它做什么”是两件事。发现层只负责能力匹配,策略层必须再次结合用户、设备、数据范围和动作风险做决策。
type CapabilityDecision =
| { allowed: true; scope: string[] }
| { allowed: false; reason: 'UNTRUSTED_AGENT' | 'NO_CONSENT' | 'SCOPE_DENIED' }
function authorize(card: AgentCard, task: TaskRequest,
session: UserSession): CapabilityDecision {
if (!trustStore.contains(card.identity)) {
return { allowed: false, reason: 'UNTRUSTED_AGENT' }
}
if (!session.scopes.includes(task.requiredScope)) {
return { allowed: false, reason: 'SCOPE_DENIED' }
}
return { allowed: true, scope: [task.requiredScope] }
}
这是领域层伪代码。实际身份与授权应使用官方能力,不要自造加密或把凭据写进 Agent Card。Card 可以说明需要什么认证,但绝不能携带长期令牌。
四、把每次请求建模为任务
官方 A2A 指导包含接收请求、触发业务逻辑、更新任务状态和返回结果。工程上应为每次调用分配 taskId,并维护可穷尽状态,而不是只返回一个 Promise。
RECEIVED
↓
VALIDATING ─────→ REJECTED
↓
RUNNING ────────→ FAILED
├────────────→ CANCELLED
↓
COMPLETED
建议约束:终态不可再次迁移;CANCELLED 后业务不得继续提交;COMPLETED 必须有结果摘要;FAILED 必须有稳定错误码和是否可重试标记。
type TaskState =
| 'RECEIVED' | 'VALIDATING' | 'RUNNING'
| 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'REJECTED'
interface TaskSnapshot<T> {
taskId: string
state: TaskState
progress?: number
result?: T
errorCode?: string
retryable?: boolean
updatedAt: number
}
五、AgentAbilityExtension 只做协议入口
在 ArkTS 应用中,A2A 服务端可以通过 AgentAbilityExtension 承接请求。Extension 层不应直接拼页面或写数据库,而应做四件事:解析协议、校验身份、创建任务、交给领域用例。
// 协议骨架:生命周期和回调名称以 API 26 当前文档为准
class TravelAgentEntry {
async handle(request: A2ARequest): Promise<A2AResponse> {
const parsed = requestParser.parse(request)
const decision = policy.authorize(parsed)
if (!decision.allowed) return responses.rejected(decision.reason)
const task = await taskStore.createOrGet({
idempotencyKey: parsed.idempotencyKey,
skillId: parsed.skillId,
callerId: parsed.callerId
})
taskRunner.start(task.id, parsed.command)
return responses.accepted(task.snapshot())
}
}
领域层只认识“查询天气”“创建日历事件”等命令,不认识 A2A 报文。这样协议升级时,只需修改适配层。
六、幂等键必须覆盖副作用
A2A 调用可能出现经典的“不确定成功”:服务端已经写入日历,但结果回传前调用方超时。调用方重试后,如果服务端再创建一次,就会出现重复事件。
async function createCalendarEvent(cmd: CreateEventCommand): Promise<EventResult> {
const previous = await resultStore.find(cmd.idempotencyKey)
if (previous) return previous
const event = await calendar.create(cmd.payload)
const result = { code: 'OK', eventId: event.id }
await resultStore.save(cmd.idempotencyKey, result)
return result
}
生产实现要考虑“副作用成功、结果保存失败”的事务边界。优先使用业务唯一键或可原子提交的存储策略,不要只依赖进程内缓存。
七、长任务要有进度、取消和超时
旅行计划可能依次查询天气、交通和日历。若只有 RUNNING,用户无法判断卡在哪一步。建议对外返回稳定阶段,而不是泄露内部线程或组件名。
| progress | stage | 用户可见含义 |
|---|---|---|
| 10 | validating | 正在检查日期与权限 |
| 35 | fetching-context | 正在获取天气与交通信息 |
| 70 | composing | 正在生成计划草案 |
| 90 | awaiting-confirmation | 等待确认是否写入日历 |
| 100 | completed | 已完成 |
取消不是“把状态改成 CANCELLED”就结束。任务执行器必须感知取消信号,停止后续请求,清理临时数据,并阻止迟到结果覆盖终态。
async function transition(taskId: string, next: TaskState, version: number) {
const current = await taskStore.get(taskId)
if (current.version !== version) throw new ConflictError()
if (isTerminal(current.state)) return current
assertAllowedTransition(current.state, next)
return taskStore.update(taskId, next, version + 1)
}
版本号可以阻止并发更新互相覆盖。系统回收进程后,任务也应能从持久化快照恢复或明确失败,不能永久停在 RUNNING。
八、返回结构化结果和证据时间
智能体结果不应只有一段自然语言。天气建议至少返回来源时间、城市、日期、结构化预报和置信边界;行程写入则返回事件 ID 和可撤销入口。
interface TravelPlanResult {
planId: string
status: 'DRAFT' | 'SAVED'
days: Array<{ date: string; items: PlanItem[] }>
evidenceTime: string
warnings: string[]
undoToken?: string
}
自然语言摘要可以由展示层生成,但业务真相必须来自结构化结果。过期天气或缺失交通数据要进入 warnings,不能被“计划已生成”掩盖。
九、错误码要让调用方知道下一步
建议错误至少覆盖:未知 Agent、未知技能、版本不兼容、认证失败、授权不足、参数非法、业务冲突、用户取消、执行超时、暂时不可用和内部错误。
{
"taskId": "task-20260828-001",
"state": "FAILED",
"error": {
"code": "DEPENDENCY_TIMEOUT",
"retryable": true,
"retryAfterMs": 3000,
"userMessage": "天气服务暂时无响应,可稍后重试"
}
}
内部堆栈不能直接返回给另一个 Agent;对外保留稳定语义,对内用 traceId 关联详细日志。
十、可观测性要贯穿整个调用链
一次 A2A 协作建议统一关联 traceId、taskId、callerAgentId、skillId、Card 版本和幂等键摘要。指标不要只看总耗时,而要拆为发现、鉴权、排队、执行、确认等待和结果封装。
敏感内容只记录类型、长度或哈希摘要,不记录联系人、精确行程和令牌正文。调试便利不能凌驾于用户隐私。
十一、测试矩阵必须包含乱序和恢复
describe('A2A task state', () => {
it('does not let a late success override cancellation', async () => {
await store.transition(id, 'RUNNING')
await store.transition(id, 'CANCELLED')
await runner.reportCompleted(id, fixtureResult)
expect((await store.get(id)).state).toBe('CANCELLED')
})
it('reuses the result for the same idempotency key', async () => {
const a = await entry.handle(fixtureRequest)
const b = await entry.handle(fixtureRequest)
expect(b.taskId).toBe(a.taskId)
expect(calendar.createCount).toBeLessThanOrEqual(1)
})
})
除成功路径外,还要覆盖:伪造 Card、技能不存在、权限过期、参数边界、用户中途撤回授权、依赖超时、应用被回收、结果乱序、重复请求和不支持 API 26 的设备。
十二、上线检查清单
- Agent Card 只声明真实上线并经过测试的技能;
- 客户端校验身份、版本、认证方式和字段兼容性;
- 能力发现不直接等价为用户授权;
- 每次请求都有 taskId、幂等键和可追踪终态;
- 取消、超时、恢复和迟到结果都有确定规则;
- 写操作在提交前展示变更摘要,并提供撤销或补偿;
- 结果是结构化数据,包含证据时间和警告;
- 日志可关联全链路,同时完成敏感信息最小化;
- 真机验证前台、后台、进程回收与多 Agent 并发。

结语
端侧 A2A 的价值,是让应用从孤立入口变成可协作的能力节点;它的工程难点,则是把“智能调用”约束为一套可验证的分布式任务。把 Agent Card 当版本化契约、把请求当状态机、把副作用放进幂等领域服务,再补齐取消、恢复和审计,A2A 才能从演示走向生产。
官方参考
- 通过 AgentAbilityExtension 实现智能体间 A2A 通信:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hmaf-a2a-dev-guide
- HarmonyOS 7 新能力一览:https://developer.huawei.com/consumer/cn/features/
更多推荐



所有评论(0)