- 人工智能
- AI Agent
- Agent 框架
- 后端
- 工具调用
【免费下载链接】eve
The Open Framework for Building Agents
本文是 eve 开源框架(The Open Framework for Building Agents)在 "Upgrade to Tasks" 版本中关于后台任务(background tasks)升级为 Tasks 体系的完整迁移实战指南。它面向已经使用过 eve 0.66 之前 API(execution: "background"、dismissible、ctx.agent(name, { message, agentId })、subagent.*事件等)的开发者,逐节给出破坏性变更的替换代码、错误语义变化、客户端与服务端的事件协议差异,以及远端 Agent(remote agent)协议的升级顺序。读完本文,你将能一次性识别并改写工作流工具、Agent 调用、客户端流处理、Hooks 与 evals 中所有受影响的代码路径,平稳升级到新的 Tasks API。
本版本的迁移核心一句话概括:后台任务被task(及serve)取代,agentId延续被taskId取代,subagent.*事件被task.*/agent.started事件取代,turn.completed/session.waiting的语义被turn.waiting收紧。下面对照 eve 官方文档 docs/tools/tasks-upgrade.md 的八个小节逐一展开,并结合仓库源码与测试验证每一条迁移规则。
一、迁移总览:哪些代码会变,哪些保持不变
先建立一个全局判断标准:不是每个工作流工具都需要改。如果一个工作流工具没有使用execution: "background"、dismissible或ctx.agent这三者中的任何一个,它的代码可以原样保留:
execute(input, ctx)的函数签名不变;ctx.abortSignal与ctx.callId仍在原来的位置。
但所有execute工具都有一项行为变化:一条转向消息(steering message)现在会中止该调用的ctx.abortSignal(详见下文"去掉dismissible"一节)。也就是说,旧版里"问题可以跨消息保持开放"的行为被统一收口为"会话向前推进时,execute调用随之终止"。
好消息是,绝大多数代码变更都可以被 TypeScript 类型检查直接标记出来——被移除的execution、dismissible选项以及旧的ctx.agent调用签名都会产生类型错误。从 eve 的兼容性说明(packages/eve/src/compiler/extension-compatibility.ts)可以看到对应的版本行为描述:"Workflow tools no longer accept execution or return background task receipts, and every call blocks until it settles. ctx.ask() no longer accepts dismissible… ctx.agent(name) returns a session handle whose send() delivers a message and whose result() resolves the turn."
本文覆盖的迁移面如下:
execution: "background"→task(input, ctx)dismissible选项移除 → 转向消息中止executectx.agent(name, { message, agentId })→ctx.agent(name)会话句柄- 模型侧 Agent 工具参数
agentId→taskId - 后台投递选项(
taskDeliveryPolicy等)整体移除 subagent.*流事件 →task.*/agent.started事件- 客户端对"开放回合(open turn)"的新处理(
turn.waiting) - 远端 Agent 先于调用方升级(remote agent 协议 v2)
二、把execution: "background"替换为task(input, ctx)
2.1 机械替换:改名 + 删选项
后台任务的核心写法是把execute与execution: "background"组合使用;新 API 中直接定义task(input, ctx),函数体完全不用动:
export default defineWorkflowTool({ description: "Deploy a service.", inputSchema: z.object({ service: z.string() }), - execution: "background", - async execute({ service }, ctx) { + async task({ service }, ctx) { "use workflow"; return await deploy(service, ctx.abortSignal); }, });如果遗漏了删除execution选项,defineWorkflowTool会在工具定义时直接抛错。仓库中的校验逻辑位于 packages/eve/src/tools/workflow-definition.ts:
if ("execution" in definition) { throw new Error( '"execution" was replaced by task(). Define task(input, ctx) to run each call as a task.', ); }对应的测试用例 packages/eve/src/tools/workflow-definition.test.ts 也验证了这一错误信息。同理,一个工作流工具必须恰好定义一个入口点——execute、task、serve三选一,定义零个或多个都会在定义时抛出Define exactly one of execute(input, ctx), task(input, ctx), or serve(receive, ctx); this tool defines ${found}.(见 workflow-definition.ts)。
2.2task与execute的运行时差异
| 维度 | execute(input, ctx) | task(input, ctx) |
|---|---|---|
| 运行方式 | 普通工具调用,回合等待其结果 | 任务:立刻返回收据,结果稍后到达 |
| 结果到达 | 工具结果 | 收据 + 后续task.result消息 |
| 转向消息打断 | 会中止其abortSignal | 不会打断(task 无感) |
| Context | WorkflowToolContext | 同样的WorkflowToolContext |
task函数体拿到的WorkflowToolContext与execute完全相同,且转向消息永远不会中止 task 的abortSignal——这正是任务与普通调用的分水岭:任务在会话继续对话时照常运行。关于三种入口点的完整选择逻辑可参考 eve 官方文档 docs/tools/tasks.md 中的"Choose how a call runs"。
2.3 需要持续接收输入的场景改用serve
如果模型在任务运行期间还需要继续向它发送新输入(典型例子:模型会按用户要求反复修订的一份计划),那么task也不够用,应改而定义serve(receive, ctx)。serve是"可恢复任务(resumable task)"入口:每次新调用通过receive()解析,并可用ctx.reply()回复。其语义在 docs/tools/workflows.mdx 的 "Resumable tasks:serve" 一节有完整说明,eve 的 Agent 工具正是以serve工具实现的(详见第五节)。三入口的判定见 docs/tools/tasks.md:
defineWorkflowTool({ /* … */ async execute(input, ctx) {} }); // 回合等待调用结束 defineWorkflowTool({ /* … */ async task(input, ctx) {} }); // 任务:先收据,后结果 defineWorkflowTool({ /* … */ async serve(receive, ctx) {} }); // 可接收更多调用的任务三、去掉dismissible:新消息现在会中止execute调用
旧版中,ctx.ask()支持dismissible: true,使一个问题在对话继续时可以保持打开。新版移除了该选项——转向消息会中止当前回合等待中的每个execute调用的ctx.abortSignal,从而收回该调用提出的所有问题,效果与旧版dismissible: true一致:
-const answer = await ctx.ask({ prompt, dismissible: true }); +const answer = await ctx.ask({ prompt });3.1 中止的传导范围
凡是接收ctx.abortSignal的工作都会随之停止,包括:
- 一个
step(步骤); - 一次
ctx.agent发送(见第四节); - 问题(
ctx.ask)本身。
调用最终以函数体返回的内容落定;若函数体因中止而抛出异常拒绝,则以{ interrupted: true }落定。模型会在下一条消息中读到这一结果。
3.2 状态值变化:dismissed→cancelled
被收回的问题现在解析为{ status: "cancelled" }(而不是dismissed),相应地input.resolved结果类型中的"dismissed"也改为"cancelled"。任何按旧值做匹配的客户端与渠道都必须更新。这条语义在 docs/tools/tasks.md 的 "Pass signals to the work" 一节有更完整的描述:在serve函数体内,ctx.reply()同样会收回它结算的调用仍挂起的问题;ctx.ask(request, { signal })还支持额外的信号中止。关键是分支逻辑应判断answer.status(answered/cancelled),而不是依赖信号本身——因为"人已经先回答了"时,即使信号已中止,问题仍按answered结算。
3.3 想让问题跨消息保持开放怎么办
把问题移到task中提出(替换为task(input, ctx))即可:task 不受转向消息影响,问题保持开放直到用户回答或任务被显式取消。这也是"何时该用任务"的最典型判据之一——当模型需要一边继续对话、一边等待某个输入或长耗时工作时,就用 task。
四、用ctx.agent(name)打开 Agent 会话
4.1 旧 API 的返回值变成了会话句柄
旧签名ctx.agent(name, { message, agentId })直接返回 Agent 的输出;新签名ctx.agent(name)返回一个会话(session),你需要先send消息、再读取该回合的结果:
-const review = await ctx.agent("reviewer", { message: plan }); +const response = await ctx.agent("reviewer").send(plan, { signal: ctx.abortSignal }); +const { message: review, status } = await response.result(); +if (status === "failed") throw new Error("The review failed.");要点:
agentId参数已被移除;每次ctx.agent(name)调用都开启一个新会话;- 在同一个句柄上继续
send会延续该会话的对话,直到工作流运行结束; signal: ctx.abortSignal把工作流调用的中止信号传递给 Agent 回合,保持取消语义一致。
ctx.agent的能力边界在 docs/tools/workflows.mdx 的 "Delegate work:ctx.agent" 一节有完整说明。需要强调的是:workflow工具自动生成的程序(generated programs)仍然保留ctx.agent(name, { message, outputSchema? })能力,只有手写工作流工具的调用方式发生了变化。
4.2 使用ctx.agent保证"先审后发"的强顺序
Tasks 体系下 Agent 调用都是任务,模型可以自行决定是否等待,因此"先审核再发布"这类指令只有模型遵守时才成立。若顺序必须无条件成立,应写一个工作流工具:先用ctx.agent跑第一步(如审核),再执行副作用,并且不要把副作用暴露成独立工具。eve 文档 docs/tools/tasks.md 给出了完整的publish_reviewed_notes示例(审核通过才发布、失败抛错、可选tool: false隐藏审核 Agent、需要边聊边审时把execute换成task)。
五、用taskId延续 Agent:每个 Agent 调用都是可恢复任务
这是迁移中模型侧提示词改动最大的一处。新版里每个 Agent 工具都是serve工具,因此每次 Agent 调用都是可恢复任务(resumable task)。模型调用 Agent 工具时,参数从agentId换成taskId:
-{ "message": "Check the Q2 numbers too.", "agentId": "…" } +{ "message": "Check the Q2 numbers too.", "taskId": "researcher-7k2m9q" }5.1 调用即收据,结果稍后送达
- Agent 调用立刻返回一个**收据(receipt)**作为工具结果,例如:
Started task researcher-7k2m9q. Its result will arrive in a <task_result> message. To send it another message, call researcher again with taskId researcher-7k2m9q.; - Agent 的回复随后以一条
task.result消息到达(任务 ID 形如"工具名 + 6 个字符"); - 凡是提示词、指令、evals 中提过
agentId、或期望"回复直接作为工具结果返回"的地方,都必须更新; agentRouter()与workflow工具现在也把每次调用当作任务运行。
5.2 错误码的变化
被移除的错误码AGENT_BUSY、AGENT_MISMATCH、AGENT_UNREACHABLE、AGENT_INVOCATION_NOT_ADMITTED没有替代品。取而代之的是两个任务专属错误码:
| 错误码 | 触发条件 |
|---|---|
UNKNOWN_TASK | 调用的taskId指向不存在、或并非该工具开启的未完成任务 |
TOO_MANY_TASKS | 调用将开启新任务、或让空闲任务重新工作,而此时已有32个任务在运行 |
这两个常量定义在 packages/eve/src/protocol/task-tools.ts,抛出逻辑见 packages/eve/src/execution/tasks/start.ts。更多细节(eve__task_cancel对未知任务的反馈、32 个并发工作任务上限、任务无独立超时等)参见 docs/tools/tasks.md 与 "Ownership and limits" 一节。
六、移除后台投递选项:结果只有一种到达方式
旧版有复杂的后台结果投递机制,现在全部收口为一条路径:task.result消息在步骤边界(step boundary)到达模型。需要删除的内容:
taskDeliveryPolicy与"投递组(delivery cohorts)"。仓库的检查点迁移代码也印证了这一点——旧运行时会话状态中的eve.runtime.taskDeliveryPolicy键已被清理(packages/eve/src/execution/session/checkpoint-migrations.ts),且旧kind: "execution.background_task"消息被迁移(同文件 L272);session.cancel()的tasks选项。现在session.cancel()一次取消三样东西:当前回合、回合等待中的execute调用、以及所有正在工作的任务;它仍然接受turnId与signal。
模型视角的变化也很明显:不再看到[Task state]或[Agents]便签、散文式任务通知、<eve-empty-delivery/>或结果回合;取而代之的是列出工作任务与空闲任务的[Tasks]便签。BACKGROUND_TASK_FAILED与BACKGROUND_TASK_CANCELLED错误码被移除,失败任务在task.settled事件及其task.result块中报告错误。
七、读取task.*事件,替代subagent.*事件
四个旧事件被移除:subagent.called、subagent.started、subagent.completed、subagent.event。替换关系如下表:
| 新事件 | 替代旧事件 | 携带数据 |
|---|---|---|
task.started | subagent.called | taskId、callId、turnId、name、kind |
task.settled | subagent.completed | taskId、callId、turnId、name、kind、status,以及(未取消时)output或error |
agent.started | subagent.started | callId、turnId、taskId、name、sessionId、streamPath |
7.1 客户端:跟随子会话的流
subagent.event原本负责转发子 Agent 自身的事件;现在改为在子会话自己的流上读取。session.streamSubagent()更名为session.agent(started).stream(),且入参从旧事件变为agent.started事件:
for await (const event of session.stream()) { - if (event.type !== "subagent.called") continue; + if (event.type !== "agent.started") continue; - for await (const childEvent of session.streamSubagent(event)) { + for await (const childEvent of session.agent(event).stream()) {仓库测试证实了这一 API 形态:session.agent(startedEvent()).stream({ follow: false })出现在 packages/eve/src/client/session.test.ts 与 packages/eve/src/client/subagent-stream.integration.test.ts 中;会话句柄实现位于 packages/eve/src/client/agent-session.ts。
7.2 Hooks:订阅方式不变,事件名更新
Hooks 用同样的方式订阅新事件,只是名称和守卫条件变化:
export default defineHook({ events: { - async "subagent.completed"(event, ctx) { + async "task.settled"(event, ctx) { + if (event.data.status !== "completed") return;7.3 evals:calledSubagentAPI 不变
eval 断言t.calledSubagent(name, { status })保留原有 API,但底层改为读取任务事件。实现位于 packages/eve/src/evals/assertions/run.ts,其测试覆盖了task.started/task.settled事件上的状态匹配与计数(packages/eve/src/evals/assertions/run.test.ts)。因此,你的 eval 断言代码大多不需要改动,但读取的底层事实(facts)来源已从 subagent 事件切换为 task 事件(见 packages/eve/src/evals/runner/derive-run-facts.ts)。
八、客户端如何处理开放回合(open turns)
这是行为变化最隐蔽、也最容易造成客户端"提前截断回复"的一节。有任务在工作时,回合不会结束——一个回合可以跨越一次等待(wait)。流用新事件turn.waiting报告每次等待,并携带开放回合的turnId。以下是必须理解的几条规则:
8.1turn.waiting的三种触发场景
- 持有回合(held turns):模型在任务仍在工作时结束自己的文本,或
eve__task_wait尚无结果时,流发出turn.waiting。同一turnId的下一条step.started表示回合恢复。在根会话(root session)中,等待前的文本以正常的message.completed完成;在子会话与调度(schedule)的会话中,该步骤以finishReason: "tool-calls"报告。 - 调用内部的问题与登录不再结束回合:旧版中,运行中调用内部的问题或登录(如工作流工具的
ctx.ask()、ask_question、子 Agent 的问题)会在回合中途发出turn.completed与session.waiting。现在改为先发出input.requested或authorization.required,再发出turn.waiting,用户作答后回合在同一turnId下继续。你作答的那个会话,会为它路由到答案的每个问题发出input.resolved。注意:回合自身请求的工具审批(tool approval)仍然以turn.completed+session.waiting结束回合,这条没变。 turn.completed只在回合真正结束时发出,session.waiting永远意味着回合已结束。
8.2 客户端读取结果的新规则
TypeScript 客户端的send(...).result()会越过turn.waiting,返回持有回合的最终回复,而不是等待前写下的文本;它只在"问题待答"时停在turn.waiting,返回status: "waiting"并附带该问题,随后respond()会把同一个回合读到结束。自定义客户端如果满足以下任一情况,都应遵循同一规则(详见 docs/guides/client/streaming.mdx 的 "Aggregate a turn"):
- 在回合中途以
turn.completed或session.waiting结束响应; - 把第一条完成的
message.completed当作回复展示。
8.3 任务结果不是流事件
任务结果不会作为流事件出现。旧版后台任务结果以message.received且data.kind: "execution.background_task"出现;现在message.received不再有kind,且任务结果只到达模型。每次调用的结果应从task.settled读取。关于流的更多语义(turn.waiting的on: "input" | "tasks"字段、task.settled的cancel.reason等)见 docs/concepts/sessions-runs-and-streaming.md 的 "Task events" 一节。
8.4 消息流版本号:26
消息流(message stream)版本现在是26。TypeScript 客户端接受版本 21~26;只接受到 25 的客户端会以"unsupported-version"错误使流失败。因此升级时请同步更新客户端 SDK,而不是只改服务端。
九、先升级远端 Agent,再升级调用方
远端 Agent 协议升级到了version 2(常量REMOTE_AGENT_PROTOCOL_VERSION = 2定义于 packages/eve/src/protocol/remote-agent-protocol.ts)。升级顺序是硬约束:先把每个远端 Agent(docs/guides/remote-agents.md)部署到新版本,再升级调用它的部署。
9.1 协议 1 的兼容服务
本版本部署的远端 Agent 仍然服务 eve 0.66~0.68 的调用方(它们说的是协议 1),行为包括:
- 运行调用方的回合,把每个结果发送到调用方的回调;
- 接受调用方的后续(follow-up)与重置(reset)请求;
- 把远端 Agent 的工具审批与登录请求发送给调用方,并接受调用方的回答——与 0.66~0.68 的远端 Agent 行为一致;
- 这一兼容覆盖的是"调用方模型直接调用的 Agent"(它们作为后台任务运行);当调用方自己的工作流工具代码调用 Agent 时,其审批与登录请求停留在远端 Agent 自己的渠道上。
每次协议 1 调用都会记录一条日志serving a remote agent protocol 1 caller(带上调用方来源),实现见 packages/eve/src/eve-channel/index.ts。
9.2 与同版本远端 Agent 的两点差异
- 调用方的渠道不会在调用下方嵌套展示远端 Agent 的实时工具活动;
- 远端 Agent 中的
ctx.ask()返回unavailable,因为协议 1 的调用方不授予远端 Agent 输入能力——这一点与 0.66~0.68 保持一致。
9.3 调用方侧:协议不匹配即失败
当远端 Agent 部署仍说协议 1 时,本版本上的调用方会在调用开始时失败,并给出同时包含两个版本的错误。协议不匹配错误消息由 packages/eve/src/protocol/remote-agent-protocol.ts 生成:"Remote agent … speaks eve remote agent protocol ${receiverVersion}, but this deployment speaks protocol ${REMOTE_AGENT_PROTOCOL_VERSION}. Upgrade both deployments to the same eve release."。请务必按"先 Agent、后调用方"的顺序完成升级,避免调用中途失败。
十、升级检查清单
迁移完成后,建议按以下清单逐项自检:
- 工作流工具:
grep确认不再出现execution:与dismissible:;execute/task/serve恰好定义其一(定义时会抛错校验)。 ctx.agent调用:全部改为ctx.agent(name)会话句柄 +send()+result(),不再传{ message, agentId }。- 提示词与 evals:不再出现
agentId;Agent 工具参数统一为taskId;不再期望"回复作为工具结果直接返回"。 - 客户端:订阅
task.started/task.settled/agent.started/turn.waiting,删除subagent.*订阅与streamSubagent()调用;result()能越过turn.waiting读到最终回复;流版本号 >= 21 且支持 26。 - Hooks:事件名改为
task.settled等,注意status守卫。 - 渠道与前端:匹配
"dismissed"的地方改为"cancelled";按需适配turn.waiting的界面表现(Slack 渠道已有实时的任务卡片,见 docs/channels/slack.mdx 的 task card 说明)。 - 部署顺序:远端 Agent 先升级到本版本,再升级调用方。
如果想进一步理解新体系的完整运行模型(任务如何被模型感知、eve__task_wait/eve__task_cancel的语义、回合如何等待任务、任务归属与 32 任务上限),请继续阅读 docs/tools/tasks.md;工作流工具三种入口点的写作细节见 docs/tools/workflows.mdx。
- 人工智能
- AI Agent
- Agent 框架
- 后端
- 工具调用
【免费下载链接】eve
The Open Framework for Building Agents
相关推荐
MediaPipe技术迁移终极指南:从Legacy到Tasks的高效升级方案
MediaPipe技术迁移终极指南:从Legacy到Tasks的高效升级方案 MediaPipe是一款跨平台、可定制的机器学习解决方案,专为实时和流媒体媒体打造
人工智能机器学习计算机视觉多模态本地部署文档签名自动化:Signature Extractor 实战指南
文档签名自动化:Signature Extractor 实战指南 在数字化办公和文档管理过程中,从扫描文档中高效提取手写签名是许多企业和机构面临的实际挑战。Si
gh-stack rebase 中断恢复机制:崩溃后如何还原全部分支状态(完整指南)
gh stack rebase 中断恢复机制:崩溃后如何还原全部分支状态(完整指南) gh stack 是 GitHub 官方的 堆叠式 Pull Reques
开发工具CLI版本控制
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考