
本文只谈两件事:概念的边界与实现的方式。所有代码均取自 ooderAgent 仓库当前实现。
「Agent」在学术上是有精确定义的,而且条件相当苛刻。把四个权威来源放在一起,边界就清楚了。
来源 | 定义要点 |
|---|---|
AIMARussell & Norvig, 1995 | “An agent is anything that can be viewed as perceiving its environment through sensors and acting upon that environment through actuators.”更关键的是理性智能体与PEAS规范:Performance measure(性能度量)、Environment(环境)、Actuators(执行器)、Sensors(感知器)——没有显式目标与性能度量的,只是“会运行的程序”,不是理性智能体。 |
Wooldridge & Jennings1995 | “A computer system that is situated in some environment, and that is capable of autonomous action in this environment in order to meet its design objectives.”并给出四属性:autonomy(自主)/reactivity(反应)/pro-activeness(主动)/social ability(社会能力),且要求同时具备。 |
Ferber1995 | 智能体不孤立存在:它处在有其他智能体活动的环境中,并且属于某个组织。 |
OECD2026 | AI Agent:具备一定自主性、感知环境并行动、必要时使用工具、适应变化;Agentic AI进一步要求:多智能体协调、任务分解与委派、跨时间持续运行、最少人类监督。 |
Anthropic | 以控制权归属划界:Workflow 由代码预定义路径,Agent 由模型动态决定路径与工具调用;Agent 设计收敛为三要素——Environment / Tools / System prompt。 |
A2A Protocolv1.0.0 | 用Agent Card(身份、能力、技能、服务端点、鉴权)描述智能体;用Task 生命周期(submitted → working → input-required → completed/failed/canceled)管理协作;产出Artifact;通信要求不共享内部状态、记忆与工具。 |
把这些约束合起来,可以得出一组可判定的「真 Agent 六判据」:
判据 | 内容 | 学术出处 |
|---|---|---|
A1 身份 | 有稳定、可寻址、可授权的标识 | A2A Agent Card;Wooldridge autonomy |
A2 感知-行动闭环 | 能感知环境并施加动作改变环境 | AIMA(sensors / actuators) |
A3 自主与主动 | 不被逐条指令驱动;目标驱动时可自行发起 | Wooldridge autonomy + pro-activeness |
A4 社会能力 | 能与其它智能体(人或软件)按某种通信语言交互 | Wooldridge social ability |
A5 生命周期与持续存在 | 可独立启停/升级;跨时间持续运行 | OECD 持续运行;A2A Task lifecycle |
A6 组织归属 | 处在组织之中(多个智能体共享环境) | Ferber;OECD 协调 / 委派 |
一句话:Agent 不是“能聊天的模型”,而是“有身份、有目标、有环境、有同伴、有时间”的执行体。
ooderAgent 把上述判据落成五特征 + 一个差异化:
编号 | 特征 | 判定问题 | 对应判据 |
|---|---|---|---|
R-A1 | 独立身份 | 有稳定agentId(可寻址、可授权)? | A1 |
R-A2 | 独立入口 | 有自己的入口(独立页 / 独立端点),不借他人入口? | A2 |
R-A3 | 独立生命周期 | 可独立启动/停机/升级,状态可观测? | A5 |
R-A4 | 独立场景范围 | 归属某个 scene 作为范围边界? | A6 |
R-A5 | 永久在线 | 常驻(含定时/事件驱动),而非“人一发起才存在”? | A5 / A3 |
差异化 | 它是场景的 owner | 知识库、LLM 配置、技能、参与者模型、workflow全部继承自内置场景;Agent 唯一多出来的东西是“拥有这个场景,并对该场景的产出负责” | Ferber 组织维度 + Anthropic 三要素的组织化封装 |
两条反例判据同样是定义的一部分:
编号 | 规则 |
|---|---|
R-D1 | 由业务拆分后的阶段性任务声明(不是技术组件) |
R-D2 | 必须继承内置场景特性(知识、LLM、技能、参与者模型、workflow) |
R-D3 | 必须同时给出身份与对方地址(触达),否则不可被 A2A 寻址 |
R-D3 与 A2A 的 Agent Card 同构:身份 + 技能 + 端点,缺一不可。
A2A 底座不寄生于任何一个业务模块,而是四层各司其职。

传输对上层而言必须是一个端口(port)。消息队列、Topic 约定、断线重连都属于这一层,协议层只依赖它的契约:
/** L1 传输端口:所有方法不得抛异常,失败一律返回 false 或记日志,由上层决定降级 */
public interface MqttTransportPort {
boolean connect(String brokerUrl, String clientId, String user, String pwd);
boolean publish(String topic, String payload);
boolean subscribe(String topicFilter);
void addHandler(BiConsumer<String, String> handler);
void disconnect();
}契约里“不得抛异常”是设计的一部分:传输不可用时,协议栈应降级而不能雪崩。
消息模型保留协作所需的最小字段,并把“谁干”与“发给哪个节点”彻底分开:
public class A2AMessage {
private String messageId; // 幂等键
private String conversationId; // 归属会话
private String sceneGroupId; // 组播分组
private String fromAgentId;
private String toAgentId; // 执行体:谁干
private String targetNodeId; // 目标节点:发给谁(与 toAgentId 语义分离)
private A2AMessageType messageType; // TASK_REQUEST / TASK_RESPONSE / DATA_REQUEST ...
private Object payload;
private Map<String, Object> headers; // headers.requestId 用于请求-响应关联
}寻址由“节点收件箱”与“场景组播”两套 Topic 承担:
// 定向:只投递到目标节点的收件箱
mqttTransport.subscribe(MqttEventTransport.targetedTopic(localNodeId)); // ooder/node/{nodeId}/inbox
// 组播:按场景组广播
mqttTransport.publish("ooder/event/A2A/" + group, wrapPayload(message)); // ooder/event/A2A/{group}定向投递前必须解析出节点,解析不到就不发布并告警,语义上等于“绝不静默丢弃”:
String targetNodeId = resolveTargetNodeId(message); // 显式 targetNodeId → 目录 nodeId
if (targetNodeId == null || targetNodeId.isEmpty()) {
log.warn("[A2A-MQTT] 定向投递跳过:无法解析目标节点(agent={})", message.getToAgentId());
return false;
}执行端注册是“感知-行动闭环”的落点:按目录为每个执行体注册消费者;未注册的执行体在消息到达时按目录惰性注册。
@Override
public void registerHandler(String agentId, A2AMessageHandler handler) {
if (agentId == null || agentId.isEmpty() || handler == null) return;
handlers.put(agentId, handler);
if (routeAgent != null) {
try { routeAgent.registerAgent(agentId, null, handler); }
catch (Exception e) { log.warn("[A2A] 路由登记失败(handler 已注册,非致命)", e); }
}
}装配时序同样属于协议设计:目录驱动的批量注册必须在所有启动 Runner 之后再做一次,否则名册尚未恢复。
@Bean
public ApplicationListener<ApplicationReadyEvent> a2aReadyRefresh(
ObjectProvider<A2AProtocolService> ps, ObjectProvider<AgentDirectoryPort> dir,
A2AMessageRouter router) {
return evt -> {
A2AProtocolService svc = ps.getIfAvailable();
if (svc != null) svc.refreshHandlersFromDirectory(); // 注册执行端
AgentDirectoryPort d = dir.getIfAvailable();
if (d != null) d.listAgents().forEach(router::registerAgent); // 填充 capability/role 索引
};
}目录回答“有哪些执行体、谁在线、能力与角色是什么、在哪个节点”,字段集与 A2A Agent Card 同构:
public class AgentDescriptor {
private String agentId; // 身份(Agent Card: identity)
private String name;
private String role; // 角色路由
private String sceneGroupId; // 场景范围(组织归属)
private String nodeId; // 触达地址(Agent Card: endpoint 的最小形态)
private boolean online; // 生命周期 / 在线
private List<String> capabilities; // Agent Card: skills / capabilities
}协议层只依赖端口,不依赖任何具体目录实现:
public interface AgentDirectoryPort {
List<AgentDescriptor> listAgents();
AgentDescriptor find(String agentId);
boolean isOnline(String agentId);
}接入层提供执行体的注册、列举、注销与派发诊断,使“身份 + 触达”可被真实配置与核验:
@PostMapping("/agents") // 注册:agentId + 场景 + 角色 + 能力 + nodeId
@GetMapping("/agents") // 列举:同时返回“协议层视角”(目录端口)与场景目录计数
@DeleteMapping("/agents/{agentId}") // 注销:目录 + 协议栈 handler + 名册 三者同步
@PostMapping("/dispatch") // 派发:toAgentId / targetNodeId / messageType / payload点名注销时三者同步,语义才闭合:
acm.unregisterAgent(agentId);
a2aProtocolService.unregisterHandler(agentId); // 停止消费
rosterStore.remove(agentId); // 移出持久化名册A2A 交互由三种方式触发,语义收敛为四类。
通道 | 入口 | 触发者 |
|---|---|---|
①LLM 工具 | 执行体的函数调用工具面 | 模型自主判断 |
②交互面板 | HUMAN 节点操作栏 | 场景内参与者(人) |
③预定义埋线 | 流程定义(operations/timeoutPolicy/a2aRules/ 事件订阅) | 设计期埋点,运行期自动 |
四类语义:委派/指定、中断/等待、结束转交、请求-响应回数。
②通道由统一的 HumanOperation 枚举承载,委派类操作需要 DESIGNER 显式声明,形成“可委托范围”的硬边界:
public enum HumanOperation {
DELEGATE_H2H("DELEGATE_H2H", "委托他人", true, "ri-user-shared-line"),
DELEGATE_H2A("DELEGATE_H2A", "委托Agent", true, "ri-robot-line"),
DELEGATE_H2T("DELEGATE_H2T", "委托Task", true, "ri-task-line"),
INTERVENE_EXCEPTION("INTERVENE_EXCEPTION", "例外介入", false, "ri-error-warning-line");
// 第三个参数 = 是否要求 DESIGNER 定义(不在 operations 白名单内即拒绝执行)
}提交统一走一个端点,响应把“派发结果”作为一等数据返回,使“人把活交给 Agent”成为可观测事件:
// POST /api/studio/chat/human-op
// {operation:"DELEGATE_H2A", activityId, processInstId, delegateContext:{targetId}}
{
"h2aDispatch": {
"dispatched": true,
"agentId": "fin.voucher",
"executor": "UnifiedCollaborationService.createCollaborationTask(AGENT)",
"todoId": "todo-...-58c5",
"remoteTaskId": "A2A-561f476bf5a8409b"
}
}③通道的“等待/升级”由超时策略承担,红线节点超时不放行,而是升级人工:
switch (effective) {
case ESCALATE_HUMAN: // 不放行 + 发布升级人工信号
log.warn("[BuildConfirmManager] 用户交互超时({}s),升级人工(不放行)", timeoutSeconds);
signalEscalation(entry, "confirm_timeout");
return false;
case AUTO_CONFIRM: return true; // 显式列举,新增枚举值不会被静默当作放行
default: return true;
}协作的两条轴是发起方与执行方,三条触发通道回答的是谁触发——两者正交,因此可以用一个面板承载全部象限。

象限 | 发起 → 执行 | 是否 A2A | 面板形态 |
|---|---|---|---|
P2P | 人 → 人 | 否 | 委托弹窗(选人)+ 委托回执(im_delegation) |
P2A | 人 → Agent | 是(②通道特例) | 执行体选择器(人 / Agent 分栏)+ 派发回执(todoId/remoteTaskId) |
A2P | Agent → 人 | 否(执行体内部确认) | 确认卡(红线原因 + 超时策略说明 + 决策留痕) |
A2A | Agent → Agent | 是 | 消息流(from → to、类型、状态、失败原因) |
面板的三条实现契约:
1)操作按象限分组渲染——面板只订阅节点声明的 operations 白名单,再按“人际 / 人-Agent / Agent-人 / Agent-Agent”归组,避免把不同责任面的按钮平铺在同一行:
// 面板依据流程定义声明的操作白名单渲染,未声明即不出现(责任边界由 DESIGNER 决定)
const opOrder = ['DELEGATE_H2H', 'DELEGATE_H2A', 'DELEGATE_H2T'];
const visible = declaredOperations.filter(
op => opOrder.includes(op) || baseOps.includes(op)
);2)执行体选择器与目录同源——“选谁”直接读执行体目录,人 / Agent 只是目录条目的两种类型,不需要第二套数据源:
// GET /api/studio/a2a/agents
{
"viaDirectoryPort": [
{ "agentId": "fin.voucher", "name": "凭证导入 Agent", "role": "voucher",
"sceneGroupId": "voucher-fill-scene", "nodeId": "studio-8104",
"online": true, "capabilities": ["voucher.import", "voucher.fill"] }
],
"viaDirectoryPortCount": 1,
"sceneDirectoryCount": 1
}3)回执必须结构化——派发不是“提交即结束”,h2aDispatch / todoId / remoteTaskId / 失败原因都要回流到面板,使面板能显示“谁在执行、执行到哪、为什么失败”。
能力 | 入口 / 观测点 |
|---|---|
注册执行体(身份 + 触达) | POST /api/studio/a2a/agents,日志[A2A-OPS] Agent 已注册 |
目录双视角核验 | GET /api/studio/a2a/agents(viaDirectoryPort与sceneDirectoryCount) |
启动期恢复名册 | 日志[A2A-Roster] 启动期已从持久化名单恢复 N / M 个执行体 |
执行端就绪 | 日志[A2A] CoordinatorAgent 已注册为 TASK_REQUEST 消费者/A2A handler registered |
定向派发与寻址 | POST /api/studio/a2a/dispatch;订阅ooder/node/{nodeId}/inbox |
本地执行端消费 | 日志[A2A-Local] 执行体消费任务: agentId=... |
不可达不静默 | 日志[A2A-MQTT] 定向投递跳过:无法解析目标节点 |
自环不重复执行 | 自环消息不进入本地分发 |
人 → Agent 委派 | POST /api/studio/chat/human-op(DELEGATE_H2A),响应h2aDispatch |
超时升级人工 | 日志红线节点超时保护生效/升级人工(不放行) |
真 Agent = 有身份 + 有目标 + 有环境 + 有同伴 + 有时间。
ooderAgent 的落点是把它拆成可实现的四件事:A2A 底座给出独立四层(传输、协议、目录、接入),让 Agent 拥有可寻址的身份与触达方式;执行体目录与 Agent Card 同构,让“能力可被发现”;三条触发通道让 LLM、人、预定义埋线都能发起协作;融合交互面板把四象限收敛为一个操作面,让“委派、指定、等待、转交”在同一处完成并被记录。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。