diff --git a/src/content/docs/agents/runtime/capabilities/build-a-capability.mdx b/src/content/docs/agents/runtime/capabilities/build-a-capability.mdx index 962f56f324c..e1d5754f00e 100644 --- a/src/content/docs/agents/runtime/capabilities/build-a-capability.mdx +++ b/src/content/docs/agents/runtime/capabilities/build-a-capability.mdx @@ -3,7 +3,7 @@ title: Build a capability description: Write your own reusable Lifecycle capability with LifecycleCapability, including storage, durable jobs, request handling, events, and user callbacks. pcx_content_type: how-to sidebar: - order: 7 + order: 10 products: - agents --- diff --git a/src/content/docs/agents/runtime/capabilities/index.mdx b/src/content/docs/agents/runtime/capabilities/index.mdx index b596c7de775..ca7e5cf837a 100644 --- a/src/content/docs/agents/runtime/capabilities/index.mdx +++ b/src/content/docs/agents/runtime/capabilities/index.mdx @@ -128,14 +128,14 @@ These capabilities ship with the `agents` package and are stable: These capabilities are experimental. Their APIs may change between releases: -| Capability | Import | What it does | -| ---------------------------------------------------- | ----------------- | ------------------------------------------------------------------- | -| [`Streams`](/agents/runtime/capabilities/streams/) | `agents/streams` | Stores incremental output that readers replay and follow | -| [`Sessions`](/agents/runtime/capabilities/sessions/) | `agents/sessions` | Stores conversation history as a tree of messages | -| `Queue` | `agents/queue` | Runs background callbacks one at a time, in push order | -| `Tasks` | `agents/tasks` | Runs replayable background work with journaled steps | -| `RoutedAgents` | `agents/routing` | Keeps a catalog of independent agents and forwards requests to them | -| `Browser` | `agents/browser` | Keeps one persistent Browser Run session open for the agent | +| Capability | Import | What it does | +| ------------------------------------------------------------- | ----------------- | ------------------------------------------------------------------- | +| [`Streams`](/agents/runtime/capabilities/streams/) | `agents/streams` | Stores incremental output that readers replay and follow | +| [`Sessions`](/agents/runtime/capabilities/sessions/) | `agents/sessions` | Stores conversation history as a tree of messages | +| [`Queue`](/agents/runtime/capabilities/queue/) | `agents/queue` | Runs background callbacks one at a time, in push order | +| [`Tasks`](/agents/runtime/capabilities/tasks/) | `agents/tasks` | Runs multi-step background work that resumes where it stopped | +| [`RoutedAgents`](/agents/runtime/capabilities/routed-agents/) | `agents/routing` | Keeps a catalog of independent agents and forwards requests to them | +| [`Browser`](/agents/tools/browser/) | `agents/browser` | Keeps one persistent Browser Run session open for the agent | A [harness](/agents/harnesses/), such as `PiHarness`, is a capability that runs a whole agent loop. diff --git a/src/content/docs/agents/runtime/capabilities/queue.mdx b/src/content/docs/agents/runtime/capabilities/queue.mdx new file mode 100644 index 00000000000..bea40fda39a --- /dev/null +++ b/src/content/docs/agents/runtime/capabilities/queue.mdx @@ -0,0 +1,134 @@ +--- +title: Queue +description: Run background work after a request returns with the Queue capability. Items run one at a time in push order, retry on failure, and survive restarts. +pcx_content_type: reference +sidebar: + order: 7 + badge: + text: Experimental +products: + - agents +--- + +import { Render, TypeScriptExample } from "~/components"; + + + +`Queue` lets your agent answer a request right away and do the slow part afterwards. Push an item, return a response, and the queue runs the item's callback in the background. If the callback throws, the queue retries it. If the Durable Object restarts before an item runs, the item is still there and runs when the object wakes. + +`Queue` from `agents/queue` stores each item as a callback name and a payload. Items run one at a time, in the order you pushed them. `Queue` is not [Cloudflare Queues](/queues/). It does not need a binding, and its items stay inside one Durable Object. + +## Install the capability + +Register callbacks by name, then install the queue on the [Lifecycle](/agents/runtime/lifecycle/): + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { getCurrentAgent, Lifecycle } from "agents/lifecycle"; +import { Queue, type QueueItem } from "agents/queue"; + +type Upload = { fileName: string; bytes: number }; + +export class UploadAgent extends DurableObject { + readonly queue = new Queue({ + callbacks: { + processUpload: (payload: Upload, item: QueueItem) => { + const { agent } = getCurrentAgent(); + console.log( + `${agent?.lifecycle.name}: processed ${payload.fileName} (${item.id})`, + ); + }, + }, + retry: { maxAttempts: 5, baseDelayMs: 500, maxDelayMs: 10_000 }, + onError: (error) => console.error("Upload failed", error), + }); + readonly lifecycle = Lifecycle.install(this).use(this.queue); + + async onRequest(request: Request): Promise { + const upload = await request.json(); + const item = await this.queue.push("processUpload", upload); + return Response.json({ id: item.id }, { status: 202 }); + } +} +``` + + + +`push()` returns once the item is stored, so the request does not wait for the callback. The callback runs later, in its own invocation. It does not see the request or the connection that pushed the item, but it runs inside your object's context, so [`getCurrentAgent()`](/agents/runtime/lifecycle/get-current-agent/) returns the object. + +Each item stores the callback's name, not the function. Register every callback in the field initializer, so an item pushed before a restart finds its callback after the object wakes. `push()` checks the callback name and the payload type against the map. + +## Options + +| Option | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------ | +| `callbacks` | Named callbacks `(payload, item) => unknown` | +| `retry` | Default retry policy for callbacks: `{ maxAttempts, baseDelayMs, maxDelayMs }`. Defaults to `3`, `100`, and `3000` | +| `onError` | `(error) => void`. Observes an item's failure after its last attempt | + +## Push items + +`push(callback, payload?, options?)` stores one item and returns it: + +```ts +await this.queue.push("processUpload", upload, { + id: `upload:${upload.fileName}`, + retry: { maxAttempts: 1 }, +}); +``` + +| Option | Description | +| ------- | ----------------------------------------------------------------------------------------------------- | +| `id` | A stable ID. Pushing an ID that is already queued replaces that item and keeps its place in the queue | +| `retry` | Overrides the queue's retry policy for this item | + +Use `id` when the same work might be pushed twice, such as a retried upload request. The second push replaces the first instead of queueing a duplicate. + +Once the Lifecycle has started, `push()` writes the item before it returns its promise. A push in the same synchronous block as your own SQL writes commits with them, so the item and your data are stored together or not at all. + +## Manage items + +| Method | Description | +| ---------------------- | ----------------------------------------------------------------------------- | +| `get(id)` | Returns one pending item, or `undefined` | +| `list(criteria?)` | Returns pending items in push order. Filter by callback with `{ callback }` | +| `cancel(id)` | Removes a pending item. Returns `true` if one was removed | +| `cancelAll(callback?)` | Removes every pending item, or every item for one callback. Returns the count | + +A `QueueItem` has these fields: + +| Field | Description | +| ----------- | -------------------------------------------------------- | +| `id` | The item's ID | +| `callback` | The name of the callback that runs the item | +| `payload` | The data passed to the callback | +| `createdAt` | When the item was pushed, as a Unix timestamp in seconds | +| `retry` | The retry policy for this item, if one is set | + +## Failures and retries + +A callback that throws is retried with exponential backoff, up to `maxAttempts`. After the last attempt, the queue drops the item and calls `onError`. There is no dead-letter queue, so record anything you need to keep inside `onError` or the callback. + +If the object is evicted while a callback runs, the Lifecycle wakes it again and the callback runs again. Make external effects idempotent. + +## Limits + +- Items run one at a time, not in parallel. A slow callback delays every other item, and every other [job](/agents/runtime/lifecycle/job-queue/) on the object. Keep callbacks short. +- There is no priority. Items run in push order. +- Payloads must be JSON-serializable. + +For a callback at a set time, on a cron, or on an interval, use [`Scheduler`](/agents/runtime/capabilities/scheduler/). For work with several steps that should resume where it stopped, use [`Tasks`](/agents/runtime/capabilities/tasks/). + +## Observability + +The queue publishes `queue:create`, `queue:retry`, and `queue:error` events to the `agents:schedule` [diagnostics channel](/agents/runtime/observability/diagnostics-channels/#schedule-and-queue-events). + +## On the Agent class + +The [`Agent` class](/agents/agent-class/) installs a `Queue`. Its `this.queue()`, `this.dequeue()`, and `this.getQueue()` methods call it, and callback names resolve to methods on the Agent. Those methods are stable. For more information, refer to [Queue tasks](/agents/agent-class/queue-tasks/). diff --git a/src/content/docs/agents/runtime/capabilities/routed-agents.mdx b/src/content/docs/agents/runtime/capabilities/routed-agents.mdx new file mode 100644 index 00000000000..b3bdf9ad0be --- /dev/null +++ b/src/content/docs/agents/runtime/capabilities/routed-agents.mdx @@ -0,0 +1,132 @@ +--- +title: Routed agents +description: Give each user an open-ended set of agents, such as one per chat, with the RoutedAgents capability. A hub Durable Object keeps the list and forwards requests and WebSockets to each agent. +pcx_content_type: reference +sidebar: + order: 9 + badge: + text: Experimental +products: + - agents +--- + +import { Render, TypeScriptExample } from "~/components"; + + + +Many applications give each user a growing set of agents, such as one per chat, document, or task. `RoutedAgents` lets one hub Durable Object per user own that set. The hub creates agents, lists them for a sidebar, and deletes them. Clients reach each agent through the hub's URL, so the hub decides which agents a user can reach. + +Each routed agent is a separate Durable Object, with its own storage, alarm, and WebSocket connections. The hub keeps a list of entries, each with an ID and your own metadata, such as a chat title. Listing, renaming, and deleting entries only touch the hub, so a user with hundreds of chats does not wake hundreds of agents to draw a sidebar. + +`RoutedAgents` is exported from `agents/routing`, alongside [`routeAgentRequest()`](/agents/runtime/routing/). + +## Install the capability + +The hub can be a plain Durable Object. The routed agents must extend the [`Agent` class](/agents/agent-class/), which includes [Think](/agents/harnesses/think/) and `AIChatAgent` agents, because deleting an entry uses the `Agent` class's teardown to delete the agent's storage. + +This hub keeps one `ChatAgent` per chat. `ChatAgent` is defined in another module: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { RoutedAgents, routeAgentRequest } from "agents/routing"; +import type { ChatAgent } from "./chat-agent"; + +type ChatMetadata = { title: string }; + +export class UserHub extends DurableObject { + readonly chats = new RoutedAgents({ + namespace: this.env.ChatAgent, + route: "chats", + }); + readonly lifecycle = Lifecycle.install(this).use(this.chats); + + async onRequest(request: Request): Promise { + if (request.method === "POST") { + const { title } = await request.json(); + const entry = await this.chats.create({ metadata: { title } }); + return Response.json(entry, { status: 201 }); + } + return Response.json(await this.chats.list()); + } +} + +export { ChatAgent } from "./chat-agent"; + +export default { + async fetch(request: Request, env: Env): Promise { + return ( + (await routeAgentRequest(request, env)) ?? + new Response("Not found", { status: 404 }) + ); + }, +} satisfies ExportedHandler; +``` + + + +| Option | Description | +| ----------- | ------------------------------------------------------------------------------------------- | +| `namespace` | The Durable Object binding for the routed agents. It can point at a class in another Worker | +| `route` | One URL-safe path segment that the capability claims, such as `"chats"` | + +Install `RoutedAgents` before [`WebSockets`](/agents/runtime/capabilities/websockets/). `WebSockets` accepts every WebSocket upgrade, so an upgrade meant for a routed agent must reach `RoutedAgents` first: + +```ts +readonly lifecycle = Lifecycle.install(this).use(this.chats).use(this.webSockets); +``` + +## Route requests + +`routeAgentRequest()` sends requests to the hub. The hub forwards requests under `/{route}/{id}` to the routed agent behind that entry, and keeps the rest of the path: + +```txt +/agents/user-hub/alice -> UserHub "alice" +/agents/user-hub/alice/chats/{id} -> the ChatAgent behind entry {id} +/agents/user-hub/alice/chats/{id}/history -> the same ChatAgent, which sees /history +``` + +An unknown or deleted ID returns `404`, and the hub's `onRequest()` never sees it. Authenticate the user before you route the request, so one user cannot reach another user's hub. For more information, refer to [Authentication](/agents/runtime/routing/authentication/). + +The routed agent answers a forwarded WebSocket upgrade and then owns the socket, so chat messages do not wake the hub. Connect to a routed agent from React with `basePath`: + +```tsx +const chat = useAgent({ + agent: "ChatAgent", + basePath: `agents/user-hub/alice/chats/${encodeURIComponent(chatId)}`, +}); +``` + +Choose a `route` that cannot appear anywhere else in the hub's paths, including the hub's own name. The capability matches the segment anywhere in the path, and a match with no entry behind it returns `404` instead of reaching your `onRequest()`. + +## Manage entries + +| Method | Description | +| --------------------------- | ---------------------------------------------------------------------------------------------- | +| `create({ metadata? })` | Creates an entry without waking the routed agent. Returns the entry | +| `get(id)` | Returns a typed stub for RPC to the routed agent, or `null` for an unknown or deleted entry | +| `list()` | Returns entries, most recently updated first | +| `setMetadata(id, metadata)` | Replaces an entry's metadata. Returns `false` for an unknown entry | +| `delete(id)` | Removes the entry and deletes the routed agent's storage. Returns `false` for an unknown entry | + +An entry has an `id`, your `metadata`, and `createdAt` and `updatedAt` times in Unix milliseconds. Clients only see entry IDs. The hub gives each routed agent a random Durable Object name that never leaves the hub, so a client cannot guess its way to another user's agent. + +Keep conversation data in the routed agent, and keep what a list or search needs, such as a title or a last message, in the entry's metadata. To keep the metadata current, have the routed agent call an RPC method on its hub that calls `setMetadata()`. + +## Delete entries + +`delete(id)` hides the entry first, then tells the routed agent to delete itself. The routed agent deletes its storage the next time it wakes. If the call fails partway, the entry stays hidden, and calling `delete(id)` again finishes the job. + +To delete the hub itself, call `await this.lifecycle.dispose()` before `this.ctx.storage.deleteAll()`. Disposal tells every remaining routed agent to delete itself, retrying each one a few times. A routed agent that is still unreachable after those retries is left behind, and the hub no longer has an entry to retry from. + +## On the Agent class + +An `Agent` can be the hub too. Construct `RoutedAgents` as a field and install it with `this.lifecycle.use()` in the constructor. `this.destroy()` disposes the capability before it deletes the hub's storage. For routing on the `Agent` class, refer to [Routing](/agents/agent-class/routing/). + +The [routing example](https://github.com/cloudflare/agents/tree/main/examples/next/routing) runs one `ChatAgent` per chat behind a plain Durable Object hub, with `WebSockets` serving the hub's own methods. diff --git a/src/content/docs/agents/runtime/capabilities/sessions.mdx b/src/content/docs/agents/runtime/capabilities/sessions.mdx index 8d1eb07688f..ce892faaca0 100644 --- a/src/content/docs/agents/runtime/capabilities/sessions.mdx +++ b/src/content/docs/agents/runtime/capabilities/sessions.mdx @@ -4,6 +4,8 @@ description: Store durable conversation history in a Durable Object with the Ses pcx_content_type: reference sidebar: order: 6 + badge: + text: Experimental products: - agents --- diff --git a/src/content/docs/agents/runtime/capabilities/streams.mdx b/src/content/docs/agents/runtime/capabilities/streams.mdx index 6f9b08a970f..46f8dc31c39 100644 --- a/src/content/docs/agents/runtime/capabilities/streams.mdx +++ b/src/content/docs/agents/runtime/capabilities/streams.mdx @@ -4,6 +4,8 @@ description: Store incremental output, such as model tokens, in a durable chunk pcx_content_type: reference sidebar: order: 5 + badge: + text: Experimental products: - agents --- diff --git a/src/content/docs/agents/runtime/capabilities/tasks.mdx b/src/content/docs/agents/runtime/capabilities/tasks.mdx new file mode 100644 index 00000000000..60a8ff4ee48 --- /dev/null +++ b/src/content/docs/agents/runtime/capabilities/tasks.mdx @@ -0,0 +1,247 @@ +--- +title: Tasks +description: Run multi-step background work that resumes where it stopped with the Tasks capability. Steps store their results, sleeps last through restarts, and failed steps retry. +pcx_content_type: reference +sidebar: + order: 8 + badge: + text: Experimental +products: + - agents +--- + +import { Render, TypeScriptExample } from "~/components"; + + + +`Tasks` runs background work that takes several steps, such as research, then a wait, then publishing. If the Durable Object restarts partway through, because of a deploy, a crash, or eviction, the work picks up from the step it was on. Steps that already finished are not repeated, so the agent does not call a model twice or publish a report twice. + +`Tasks` from `agents/tasks` runs a task definition as a sequence of named steps and stores each step's result. A run can sleep for seconds or days without keeping the object in memory. A step that throws retries after a delay. + +## Install the capability + +Declare task definitions by name, then install the capability on the [Lifecycle](/agents/runtime/lifecycle/): + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { generateText } from "ai"; +import { Lifecycle } from "agents/lifecycle"; +import { createAI } from "agents/models/ai-sdk"; +import { Tasks, type TaskStep } from "agents/tasks"; + +type ReportInput = { reportId: string; topic: string }; + +export class ReportAgent extends DurableObject { + readonly tasks = new Tasks({ + definitions: { + "build-report@v1": async (input: ReportInput, step: TaskStep) => { + await step.status("Researching"); + const draft = await step.do( + "draft", + { retries: { limit: 4, delay: "2 seconds", backoff: "exponential" } }, + ({ signal }) => this.draft(input.topic, signal), + ); + + // Give an editor time to review before publishing. + await step.sleep("editorial-hold", "1 hour"); + + await step.status("Publishing"); + await step.do("publish", ({ idempotencyKey }) => + this.publish(input.reportId, draft, idempotencyKey), + ); + return { reportId: input.reportId }; + }, + }, + }); + readonly lifecycle = Lifecycle.install(this).use(this.tasks); + + async onRequest(request: Request): Promise { + const input = await request.json(); + const receipt = await this.tasks.run("build-report@v1", input, { + idempotencyKey: `report:${input.reportId}`, + }); + return Response.json(receipt, { status: 202 }); + } + + async draft(topic: string, signal: AbortSignal): Promise { + const ai = createAI({ binding: this.env.AI }); + const { text } = await generateText({ + model: ai("anthropic/claude-opus-5.5"), + prompt: `Write a one-page report about ${topic}.`, + abortSignal: signal, + }); + return text; + } + + async publish( + reportId: string, + body: string, + idempotencyKey: string, + ): Promise { + await this.env.REPORTS.put(`reports/${reportId}.md`, body, { + customMetadata: { idempotencyKey }, + }); + } +} +``` + + + +The `definitions` map is rebuilt every time the object starts, and each run stores only its definition's name. A run that was in progress before a restart finds its handler after the object wakes. To change the steps of a definition while runs are in progress, add a new name, such as `"build-report@v2"`, instead of editing the old one. + +Handlers and step callbacks run inside your object's context, so [`getCurrentAgent()`](/agents/runtime/lifecycle/get-current-agent/) returns the object. + +## Options + +| Option | Description | +| ------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `definitions` | Named task handlers `(input, step) => result` | +| `retries` | Default step retry policy: `{ limit, delay, backoff }`. Defaults to five attempts, starting at one second, with exponential backoff | +| `stepTimeout` | Default timeout for one step attempt, in milliseconds or as a string such as `"30 seconds"`. Defaults to five minutes | +| `onError` | `(error) => void`. Observes a run that fails | + +## Start a run + +`run(definition, input?, options?)` stores the run and returns a receipt without waiting for the run to finish: + +```ts +const receipt = await this.tasks.run( + "build-report@v1", + { reportId: "42", topic: "durable execution" }, + { idempotencyKey: "report:42" }, +); +``` + +| Option | Description | +| ---------------- | ------------------------------------------------------------------------------------------------------- | +| `idempotencyKey` | A second `run()` with the same key joins the existing run instead of starting another | +| `runId` | Chooses the run ID yourself. Generated when omitted | +| `metadata` | JSON stored with the run | +| `retain` | Keeps the run's record after it finishes. Defaults to `true`. Pass `false` to delete it when it settles | + +The receipt has the `runId`, the `definition`, the run's `state`, and `createdAt`. `accepted` is `false` when `run()` joined an existing run. + +Inputs, step results, metadata, and final results must be JSON-serializable and at most 1 MiB each. + +`tasks.handle(definition)` returns a handle for one definition. Its `run()`, `get()`, `getByIdempotencyKey()`, and `cancel()` see only that definition's runs, and `get()` types the result from the handler: + +```ts +const buildReport = this.tasks.handle("build-report@v1"); +const run = await buildReport.get(receipt.runId); +if (run?.state === "completed") { + console.log(run.result.reportId); +} +``` + +## Steps + +A handler receives a `step` object: + +| Method | Description | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| `step.do(name, config?, fn)` | Runs `fn` and stores its result. When the handler runs again, returns the stored result without running `fn` | +| `step.sleep(name, duration)` | Waits for a duration, in milliseconds or as a string such as `"1 day"`. The object can leave memory while it waits | +| `step.sleepUntil(name, when)` | Waits until a `Date` or a Unix timestamp in milliseconds | +| `step.status(message)` | Updates the run's progress message | +| `step.idempotencyKey(name)` | Returns the key that `step.do(name, ...)` passes to its callback | + +Each `step.do()` attempt receives `{ attempt, idempotencyKey, signal }`. The signal aborts when the run is cancelled or the attempt times out. Pass `config` to set `retries` and `timeout` for one step. + +A step that throws retries after a delay, up to its retry limit. Then the run fails. To fail the run on the first error, throw `NonRetryableError` from `agents/tasks`. + +## Write handlers that resume safely + +Each time a run resumes, the handler runs again from its first line. Completed steps return their stored results, and work continues from the first unfinished step. To keep that safe: + +- Put every external side effect inside a `step.do()`. +- Keep code between steps fast, and make it give the same answer every time. Capture `Date.now()` and random values as step results before you branch on them. +- Give steps in a loop stable names, such as `` `import:${index}` ``. +- Pass the attempt's `idempotencyKey` to external services. A step interrupted partway runs again, so it can take effect twice unless the service drops the repeat. + +`step.interrupted` is `{ name, attempt }` when the previous attempt stopped partway through a step, and `null` otherwise. Check it before you repeat work that cannot be undone: + +```ts +if (step.interrupted?.name === "deliver") { + // The delivery might have happened. Check before sending again. +} +``` + +If a run resumes and finds stored steps its code could not have written, it fails with `TaskReplayDivergedError` or `DuplicateTaskStepError`. If a deploy removes a definition that has runs in progress, those runs fail with `MissingTaskDefinitionError`. + +## Inspect and cancel runs + +| Method | Description | +| -------------------------- | ------------------------------------------------------------------------------------------------------------ | +| `get(runId)` | Returns a snapshot of one run, or `null` | +| `getByIdempotencyKey(key)` | Returns the run started with that key, or `null` | +| `list(options?)` | Returns runs, newest first. Filter with `definition`, `status`, and `limit` | +| `cancel(runId, reason?)` | Cancels a run that has not finished. Returns `true` if it did | +| `delete(options?)` | Deletes finished runs and their steps. Filter with `status`, `settledBefore`, and `limit`. Returns the count | + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { Tasks, type TaskStep } from "agents/tasks"; + +export class ReportAgent extends DurableObject { + readonly tasks = new Tasks({ + definitions: { + "build-report@v1": async (input: { topic: string }, step: TaskStep) => + step.do("draft", () => `A report about ${input.topic}`), + }, + }); + readonly lifecycle = Lifecycle.install(this).use(this.tasks); + + // An RPC method, so it starts the Lifecycle itself. + async cleanUp(): Promise { + await this.lifecycle.start(); + const stuck = await this.tasks.list({ status: "waiting" }); + for (const run of stuck) { + await this.tasks.cancel(run.runId, "superseded"); + } + return this.tasks.delete({ + settledBefore: new Date(Date.now() - 24 * 60 * 60 * 1000), + }); + } +} +``` + + + +A run's `state` is one of these values: + +| State | Meaning | +| ----------- | ------------------------------------------------------------------- | +| `pending` | Accepted, and not started yet | +| `running` | An attempt is running. Has `attempt` and the latest `statusMessage` | +| `waiting` | Waiting until `wakeAt`. `reason` is `sleep` or `retry` | +| `completed` | Finished, with a `result` | +| `failed` | Finished, with an `error` that has a `name` and a `message` | +| `cancelled` | Cancelled, with an optional `reason` | + +Cancelling a waiting run settles it immediately. Cancelling a running attempt aborts its signal, and the run settles at its next step. An external effect that already happened is not undone. + +## Choose an API + +| You want to… | Use | +| ------------------------------------------------------------------ | ------------------------------------------------------ | +| Run a callback at a set time, on a cron, or on an interval | [`Scheduler`](/agents/runtime/capabilities/scheduler/) | +| Run short background callbacks in push order | [`Queue`](/agents/runtime/capabilities/queue/) | +| Run work with several steps on one object, with retries and sleeps | `Tasks` | +| Orchestrate work across services, with a dashboard | [Workflows](/workflows/) | + +## Observability + +`Tasks` publishes `task:*` events, such as `task:accepted`, `task:step:retry`, `task:attempt:interrupted`, `task:completed`, and `task:failed`, to the `agents:task` [diagnostics channel](/agents/runtime/observability/diagnostics-channels/). + +## On the Agent class + +The [`Agent` class](/agents/agent-class/) installs `Tasks` as `this.tasks`. Declare definitions on its `taskDefinitions` field instead of passing them to a constructor. Think and `AIChatAgent` use `Tasks` to continue chat turns that a restart interrupted. The `Agent` class also keeps its older [fiber APIs](/agents/agent-class/durable-execution/), `runFiber()` and `startFiber()`. diff --git a/src/content/docs/agents/runtime/index.mdx b/src/content/docs/agents/runtime/index.mdx index c7b36139f0f..dbc39f58282 100644 --- a/src/content/docs/agents/runtime/index.mdx +++ b/src/content/docs/agents/runtime/index.mdx @@ -118,6 +118,10 @@ Capabilities are optional features. Start with [Choose capabilities](/agents/run | [`MCPClientManager`](/agents/runtime/capabilities/mcp-client-manager/) | `agents/mcp/client` | Connections to MCP servers | Stable | | [`Streams`](/agents/runtime/capabilities/streams/) | `agents/streams` | Durable incremental output | Experimental | | [`Sessions`](/agents/runtime/capabilities/sessions/) | `agents/sessions` | Durable conversation history | Experimental | +| [`Queue`](/agents/runtime/capabilities/queue/) | `agents/queue` | Background callbacks in push order | Experimental | +| [`Tasks`](/agents/runtime/capabilities/tasks/) | `agents/tasks` | Multi-step background work that resumes | Experimental | +| [`RoutedAgents`](/agents/runtime/capabilities/routed-agents/) | `agents/routing` | One agent per chat or document, behind a hub | Experimental | +| [`Browser`](/agents/tools/browser/) | `agents/browser` | A persistent browser session | Experimental | To write your own capability, refer to [Build a capability](/agents/runtime/capabilities/build-a-capability/).