Agent Protocol¶
agentscope-extensions-agent-protocol 把 AgentScope 的 Harness Agent 暴露为 Agent Protocol 标准 HTTP 接口,让外部系统(CI、其他 Agent 平台、自动化任务)可以用统一的方式提交”任务”,无需关心你的 Agent 实现细节。
何时使用¶
想让 Agent 像云函数一样被远程调度。
已有团队在用 Agent Protocol 客户端,想直接接进去。
把 AgentScope Harness Agent 嵌进 Spring Boot 服务,自动暴露
/tasksREST 端点。作为 远程子 agent 的托管端,供另一个 Harness 父代理通过 HTTP 调用。
协议分层¶
AgentScope 用不同协议覆盖不同信任边界 / 交互面:
层级 |
角色 |
|---|---|
AG-UI |
面向用户的聊天 UI 事件流(浏览器 ↔ 应用) |
Agent Protocol |
内部远程子 agent / 任务 HTTP API(父 harness ↔ 远程 agent 服务) |
A2A |
外部 agent 间互操作(独立扩展;不属于本次远程子 agent 流式 / HITL 改动) |
添加依赖¶
<dependency>
<groupId>io.agentscope</groupId>
<artifactId>agentscope-extensions-agent-protocol</artifactId>
<version>${agentscope.version}</version>
</dependency>
启用方式¶
模块以 Spring Boot 自动配置形式提供,所以仅需在 Spring Boot 应用里:
注入一个
HarnessAgentBean(或自定义AgentFactory)。在
application.yml里启用:
agentscope:
agent-protocol:
enabled: true
# 可选 —— 协议控制面 TaskRecord 目录(不是执行 Agent 的 workspace)
# task-store-path: ${user.dir}/.agentscope/agent-protocol
随后会自动注册 /tasks 系列 REST 接口。
控制面 vs 执行 workspace¶
AgentProtocolTaskStore 通过独立的 ProtocolTaskRepository 持久化协议任务元数据(submit / resume /
snapshot 用的 TaskRecord)。默认根目录为 agentscope.agent-protocol.task-store-path
(${user.dir}/.agentscope/agent-protocol),落在合成桶
agents/_agentscope_protocol/tasks/ 下。
该路径与每个 HarnessAgent 自己的 WorkspaceManager(MEMORY、sessions、skills)相互独立。
多 agent 的 AgentFactory 可以为每个 agent 配置不同的 .workspace(...);协议侧按 task_id
查找始终走控制面仓库。
也可以自行提供 ProtocolTaskRepository Bean 覆盖默认实现。AgentProtocolTaskStore 只接受
ProtocolTaskRepository,不要传入执行 Agent 的 WorkspaceManager。
并发执行¶
Agent 在调用之间是无状态的——单例即可服务多个并发任务。每个任务通过 RuntimeContext 携带独立的 (userId, sessionId),状态完全隔离:
@Bean
public HarnessAgent harnessAgent() {
return HarnessAgent.builder()
.name("protocol-agent")
.model("dashscope:qwen-plus")
.build();
}
同一 session 的并发请求会自动串行化;不同 session 完全并行。
Agent 选择(AgentFactory)¶
由哪个 agent 执行任务,交给 AgentFactory bean 决定。不定义时,默认工厂对所有任务返回唯一的 HarnessAgent bean。
自定义 bean 即可按 agent_id、租户或任意自定义提交上下文字段路由:
@Bean
AgentFactory agentFactory(Map<String, HarnessAgent> agentsByName) {
return request -> {
String tenant = request.contextString("tenant");
log.info("task {} agent_id={} tenant={} resume={}",
request.taskId(), request.agentId(), tenant, request.resume());
return agentsByName.getOrDefault(request.agentId(), agentsByName.get("default"));
};
}
AgentRequest 字段:
字段 |
说明 |
|---|---|
|
任务标识,同时作为 agent 的 session id |
|
提交时请求的 |
|
用户输入;resume 运行时为空 |
|
解析自 |
|
为 |
|
原样保留的提交 |
|
仅 |
工厂每次运行调用一次——提交时一次,之后每次 /resume 再调用一次,且拿到的仍是最初的提交上下文,因此 HITL 暂停前后的路由结果保持一致。
任务并发执行时请每次返回独立实例(例如 prototype 作用域 bean)。返回 null 会让任务以错误状态结束。
上下文属性(context.attributes)¶
调用方的自定义数据放在 context.attributes 里,单独嵌一层,不与协议自身的上下文字段混在一起。除了 AgentFactory 能读到之外,这些属性还会随 RuntimeContext 进入实际执行的 agent。
它们整体挂在一个带命名空间的键下——AgentProtocolConstants.RUNTIME_CONTEXT_ATTRIBUTES_KEY(agentprotocol.context.attributes):
Map<String, Object> attributes = ctx.get(AgentProtocolConstants.RUNTIME_CONTEXT_ATTRIBUTES_KEY);
String tenant = attributes != null ? (String) attributes.get("tenant") : null;
之所以命名空间化而不是平铺成顶层键,是因为框架自身会读少量约定键:agentId 决定异步工具的唤醒路由,outboundAddress 携带网关回推地址。调用方一旦把属性命名成这些词,就会改变 agent 的行为。属性不会写进系统提示词,因此不影响模型看到的内容。
把属性提升为独立键¶
当某个工具期望直接读 ctx.get("tenant"),或需要把属性转成类型化对象时,注册 RuntimeContextCustomizer bean。RuntimeContextCustomizer.flatten 按显式白名单拷贝,并自动跳过框架保留键:
@Bean
RuntimeContextCustomizer promoteTenantKeys() {
return RuntimeContextCustomizer.flatten("tenant", "ticket_id");
}
@Bean
RuntimeContextCustomizer tenantContext(TenantService tenants) {
return (request, builder) -> {
String tenant = request.attributeString("tenant");
if (tenant != null) {
builder.put(TenantInfo.class, tenants.load(tenant));
}
};
}
所有 customizer bean 都会按 @Order 应用到每次运行,且在命名空间注入之后执行——后者覆盖前者。手写 customizer 属于可信扩展,可以写任意键,包括保留键。
从父 agent 发送属性¶
父 agent 调用远程子 agent 时有两个来源,二者合并且按调用传入的优先:
// 静态:按子 agent 声明
SubagentDeclaration.builder()
.name("researcher")
.description("远程研究员")
.url("http://remote:8080")
.remoteContextAttributes(Map.of("region", "cn"))
.build();
// 动态:按本次调用,挂在父 agent 的 RuntimeContext 上
RuntimeContext.builder()
.sessionId("sess-1")
.put(AgentSpawnTool.CTX_REMOTE_CONTEXT_ATTRIBUTES, Map.of("tenant", "acme"))
.build();
值必须可 JSON 序列化。
端点¶
提交任务¶
POST /tasks
{
"task_id": "task_123",
"agent_id": "researcher",
"input": "Summarize the latest release notes",
"context": {
"user_id": "u-1",
"parent_session_id": "sess-parent",
"stream": true,
"detail": "full",
"deny_rules": [
{
"tool_name": "bash",
"behavior": "DENY",
"source": "parent"
}
],
"attributes": {
"tenant": "acme",
"ticket_id": "INC-1001"
}
}
}
可选 context 字段:
字段 |
说明 |
|---|---|
|
写入远程 agent 的 |
|
父 session 标识(用于关联 / 追踪) |
|
调用方是否打算消费 SSE 事件 |
|
|
|
父侧 DENY 权限规则,供远程侧执行 |
|
调用方自定义键值,用于路由以及本次运行的 |
成功响应:{ "task_id", "status": "pending" }。
轮询 / 等待 / 取消¶
方法 |
路径 |
说明 |
|---|---|---|
|
|
快照( |
|
|
阻塞直到终态(或 |
|
|
请求取消 |
远程 agent 等待工具确认时,快照会报告 status: awaiting_confirm,但存储的 TaskStatus 仍为 RUNNING,因此父侧 barrier 会继续等待。
事件流(SSE)¶
GET /tasks/{taskId}/events
以 Server-Sent Events 推送 agent 进度。需要 agentscope.agent-protocol.streaming-enabled=true(默认开启)。
断线重连 / 续订:
查询参数
from_seq—— 从该序号之后开始请求头
Last-Event-ID—— 未传from_seq时使用(含义相同)
每条 SSE 消息以事件序号为 id、远程事件类型为 event、JSON 正文为 data。
事件流详细度¶
提交时的 context.detail 决定订阅方能收到多少事件,每一档都是前一档的超集:
|
事件流中的类型 |
|---|---|
|
|
|
再加 |
|
再加 |
只有 verbose 能完整复现 agent 自身的事件流。无法识别的取值按 status 处理。
除各类型专属字段外,每个事件正文还带两个字段:
字段 |
含义 |
|---|---|
|
源 |
|
源 |
两者都是增量字段:忽略它们的客户端照旧读扁平字段(text、toolCallId、status 等),早于 AGENT_EVENT 的客户端则直接跳过这类消息。
HITL 恢复¶
POST /tasks/{taskId}/resume
{
"decisions": [
{ "toolCallId": "call-1", "approved": true },
{ "toolCallId": "call-2", "approved": false }
]
}
tool_call_id 也可作为 toolCallId 的别名。需要 agentscope.agent-protocol.hitl-enabled=true(默认开启)。成功响应:{ "task_id", "status": "running" }。
与调用方父 harness 的 HITL 交互见 远程授权。
配置项¶
|
类型 |
默认 |
说明 |
|---|---|---|---|
|
boolean |
|
是否注册 |
|
boolean |
|
是否暴露 SSE |
|
boolean |
|
是否因工具确认暂停任务并接受 |
|
int |
|
每个任务的 SSE 回放缓冲区大小 |
|
long |
|
SSE 订阅最长持续时间(毫秒) |
示例:
agentscope:
agent-protocol:
enabled: true
streaming-enabled: true
hitl-enabled: true
sse-replay-buffer-size: 256
sse-timeout-ms: 10800000
关闭
enabled时(默认)即使引入依赖也不会暴露任何 REST 接口,可放心打包。
与 Workspace 配合¶
每个 task 都会拿到 WorkspaceManager 分配的隔离工作目录;任务结束后,工作区里的产物(文件、日志)会通过 Agent Protocol 的标准接口暴露出来,外部客户端可以直接拉取。