Upgrade to Tasks
Move workflow tools, agent calls, clients, hooks, and evals from background tasks to tasks.
This release replaces background tasks with tasks. Every change below breaks
the previous API. Work through the sections that apply to your agent. A workflow tool that uses
none of execution: "background", dismissible, or ctx.agent keeps its code: execute(input, ctx) keeps its signature, and ctx.abortSignal and ctx.callId are where they were. One behavior
changes for every execute tool: a steering message now aborts its ctx.abortSignal. See
Drop dismissible.
Type-checking flags most of the code changes, such as the removed execution and dismissible
options and the old ctx.agent call.
Replace execution: "background" with task(input, ctx)
Rename execute to task and remove execution. The body doesn't change:
defineWorkflowTool rejects execution with
"execution" was replaced by task(). Define task(input, ctx) to run each call as a task. A
task body gets the same WorkflowToolContext as an execute body, and steering never aborts a
task's abortSignal. An execute tool is an ordinary tool call: the turn waits for its result. See
Choose how a call runs.
A tool that the model should be able to send more input while it works, such as a plan it revises
on request, defines serve(receive, ctx) instead.
Drop dismissible: a new message stops an execute call
ctx.ask no longer accepts dismissible. A steering message now aborts the ctx.abortSignal of
each execute call the turn waits on, which withdraws every question the call asked, as
dismissible: true did:
To keep a question open through new messages, ask it from a
task instead. Other work that
receives ctx.abortSignal, such as a step or a ctx.agent send, stops too. The call settles with
what the body returns, or with { interrupted: true } if the body rejects, and the model reads the
message next.
A withdrawn question resolves as { status: "cancelled" } instead of dismissed, and the
input.resolved outcome "dismissed" is now "cancelled". Update clients and channels that
match on the old value. See Pass signals to the work.
Open agent sessions with ctx.agent(name)
ctx.agent(name, { message, agentId }), which returned the agent's output, is now
ctx.agent(name), which returns a session. Send a message and read the turn's result:
agentId is removed. Each ctx.agent(name) call returns a new session, and later send calls on
the same handle continue its conversation until the workflow run finishes. See
Delegate work. The workflow tool's generated
programs keep their ctx.agent(name, { message, outputSchema? }) capability.
Continue agents by taskId
Every agent tool is now a serve tool, so every agent call is a resumable task. The model's agent
tools take taskId instead of agentId:
An agent call returns a receipt at once, and the agent's reply arrives later in a task.result
message. Update instructions, prompts, and evals that mention agentId or expect the reply as
the tool result. agentRouter() and the workflow tool now run each call as a task too.
The removed codes AGENT_BUSY, AGENT_MISMATCH, AGENT_UNREACHABLE, and
AGENT_INVOCATION_NOT_ADMITTED have no replacement. A taskId that names no unfinished task of
that tool fails with UNKNOWN_TASK, and a call past the limit of 32 working tasks fails with
TOO_MANY_TASKS.
Remove background delivery options
Results reach the model one way: a task.result message at a step boundary. Remove these:
taskDeliveryPolicyand delivery cohorts.- The
tasksoption ofsession.cancel().session.cancel()now cancels the turn, theexecutecalls it waits on, and every working task; it still acceptsturnIdandsignal.
The model no longer sees [Task state] or [Agents] notes, prose task notifications,
<eve-empty-delivery/>, or result turns. A [Tasks] note lists its working and idle tasks
instead. The BACKGROUND_TASK_FAILED and BACKGROUND_TASK_CANCELLED codes are removed; a
failed task reports its error in task.settled and in its task.result block.
Read task events instead of subagent.* events
The subagent.called, subagent.started, subagent.completed, and subagent.event events are
removed. Use these instead:
| Event | Replaces | Data |
|---|---|---|
task.started | subagent.called | taskId, callId, turnId, name, kind |
task.settled | subagent.completed | taskId, callId, turnId, name, kind, status, and output or error unless cancelled |
agent.started | subagent.started | callId, turnId, taskId, name, sessionId, streamPath |
Follow a child's own events, which subagent.event used to relay, on its stream:
session.streamSubagent() is now session.agent(started).stream(), and it takes the agent.started event.
Hooks subscribe the same way:
The eval assertion t.calledSubagent(name, { status }) keeps its API and reads task events.
Handle open turns in clients
A turn doesn't end while tasks are working, so one turn can span a wait. The stream
reports each wait with the new turn.waiting event, which carries the open turn's turnId:
- Held turns. When the model ends its text while its tasks work, and when
task_waithas no result yet, the stream emitsturn.waiting. The nextstep.startedfor the sameturnIdmeans the turn resumed. In a root session, the text before the wait completes as a normalmessage.completed; in child sessions and a schedule's sessions, that step reportsfinishReason: "tool-calls". - Questions and sign-ins inside a call no longer end the turn. Previously, a question or
sign-in from inside a running call, such as a workflow tool's
ctx.ask(),ask_question, or a subagent's question, emittedturn.completedandsession.waitingin the middle of the turn. It now emitsinput.requestedorauthorization.required, thenturn.waiting, and the turn resumes under the sameturnIdafter the answer. The session you answer on emitsinput.resolvedfor every question it routes an answer to. A tool approval the turn itself requests still ends the turn withturn.completedandsession.waiting. turn.completedcomes only when the turn really ends, andsession.waitingalways means the turn has ended.
The TypeScript client's send(...).result() reads past turn.waiting and returns the final reply
of a held turn, not the text written before the wait. It stops at turn.waiting only while a
question is pending, and returns status: "waiting" with that question; respond() then reads the
same turn to its end. A custom client that ends a response at turn.completed or
session.waiting mid-turn, or that shows the first completed message as the reply, should follow
the same rule. See Aggregate a turn.
Task results aren't stream events. Background task results used to appear as message.received
with data.kind: "execution.background_task"; message.received no longer has kind, and a task
result reaches only the model. Read each call's outcome from task.settled instead.
The message stream version is now 26. The TypeScript client accepts versions 21 through 26. A client that accepts only versions up to 25 fails the stream with an unsupported-version error. See Turns wait for their tasks.
Upgrade remote agents before their callers
The remote agent protocol is now version 2. Deploy each remote agent before the deployments that call it. A remote agent on this release still serves callers on eve 0.66 through 0.68, which speak protocol 1. It runs their turns, sends each result to their callback, and accepts their follow-up and reset requests. It also sends the remote agent's tool approvals and sign-in requests to the caller, and accepts the caller's answers, the same way a 0.66–0.68 remote agent does. As in 0.66–0.68, this covers agents the caller's model calls, which run as background tasks. When the caller's own workflow tool code calls an agent, its approvals and sign-in requests stay on the remote agent's channel.
Two things differ from a remote agent on the caller's own release:
- The caller's channel doesn't show the remote agent's live tool activity nested under the call.
ctx.ask()in the remote agent returnsunavailable, because protocol-1 callers don't grant the remote agent input. That's unchanged from 0.66–0.68.
Each protocol-1 create logs serving a remote agent protocol 1 caller with the caller's origin.
Upgrade a caller after every remote agent it calls. A caller on this release fails a call at start, with an error that names both versions, when the remote agent's deployment speaks protocol 1.