From 53d1651c1c93abc4ebcc6bf51942bcd264319608 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 27 Oct 2026 11:10:55 +0000 Subject: [PATCH 01/11] [Agents] Move the Agent class docs under /agents/agent-class/ --- public/__redirects | 115 +++++++++++------- src/components/cf/AgentsPlatformDiagram.astro | 24 ++-- .../agents/2025-03-18-npm-i-agents.mdx | 6 +- ...5-04-07-mcp-servers-agents-sdk-updates.mdx | 2 +- .../2025-11-26-agents-resumable-streaming.mdx | 4 +- ...026-02-03-agents-workflows-integration.mdx | 2 +- .../agents/2026-02-17-agents-sdk-v0.5.0.mdx | 4 +- .../agents/2026-03-02-agents-sdk-v0.7.0.mdx | 2 +- .../agents/2026-05-13-agents-sdk-v0.12.4.mdx | 2 +- .../agents/2026-06-02-agents-sdk-v0.14.0.mdx | 2 +- .../agents/2026-06-16-agents-sdk-v0.16.1.mdx | 2 +- ...6-09-11-voice-diagnostics-turn-metrics.mdx | 2 +- .../execution => agent-class}/agent-tools.mdx | 6 +- .../callable-methods.mdx | 8 +- .../communication-channels}/chat-sdk.mdx | 0 .../chat/autonomous-responses.mdx | 12 +- .../chat/chat-agents.mdx | 10 +- .../chat/client-sdk.mdx | 8 +- .../communication-channels/chat/index.mdx | 0 .../communication-channels/email.mdx | 0 .../communication-channels/index.mdx | 2 +- .../communication-channels/slack.mdx | 0 .../communication-channels/voice.mdx | 0 .../communication-channels/webhooks/index.mdx | 8 +- .../webhooks/push-notifications.mdx | 6 +- .../durable-execution.mdx | 10 +- .../http-sse.mdx | 12 +- .../agents-api.mdx => agent-class/index.mdx} | 54 ++++---- .../internals.mdx} | 8 +- .../protocol-messages.mdx | 16 +-- .../execution => agent-class}/queue-tasks.mdx | 16 +-- .../readonly-connections.mdx | 12 +- .../execution => agent-class}/retries.mdx | 8 +- .../communication => agent-class}/routing.mdx | 8 +- .../run-workflows.mdx | 6 +- .../schedule-tasks.mdx | 14 +-- .../lifecycle => agent-class}/state.mdx | 16 +-- .../execution => agent-class}/sub-agents.mdx | 12 +- .../using-ai-models.mdx | 10 +- .../websockets.mdx | 14 +-- .../agentic-patterns/human-in-the-loop.mdx | 2 +- .../agentic-patterns/long-running-agents.mdx | 32 ++--- .../docs/agents/concepts/calling-llms.mdx | 14 +-- .../conversation-state-and-memory.mdx | 4 +- src/content/docs/agents/concepts/tools.mdx | 6 +- .../docs/agents/concepts/what-are-agents.mdx | 4 +- .../docs/agents/concepts/workflows.mdx | 2 +- .../docs/agents/examples/browser-agent.mdx | 2 +- .../docs/agents/examples/chat-agent.mdx | 6 +- .../docs/agents/examples/email-agent.mdx | 6 +- .../docs/agents/examples/slack-agent.mdx | 2 +- .../docs/agents/examples/voice-agent.mdx | 6 +- src/content/docs/agents/harnesses/index.mdx | 8 +- .../docs/agents/harnesses/think/channels.mdx | 2 +- .../agents/harnesses/think/client-tools.mdx | 2 +- .../docs/agents/harnesses/think/index.mdx | 12 +- .../agents/harnesses/think/messengers.mdx | 2 +- .../docs/agents/harnesses/think/recovery.mdx | 6 +- .../docs/agents/harnesses/think/tools.mdx | 2 +- .../docs/agents/harnesses/think/workflows.mdx | 2 +- src/content/docs/agents/index.mdx | 6 +- .../model-context-protocol/apis/agent-api.mdx | 14 +-- .../apis/client-api.mdx | 12 +- .../guides/connect-mcp-client.mdx | 2 +- src/content/docs/agents/platform/limits.mdx | 2 +- .../agents/runtime/communication/index.mdx | 12 -- .../agents/runtime/execution/agent-skills.mdx | 2 +- .../runtime/lifecycle/get-current-agent.mdx | 6 +- .../agents/runtime/lifecycle/sessions.mdx | 4 +- .../runtime/operations/configuration.mdx | 8 +- .../cross-domain-authentication.mdx | 6 +- .../operations/observability/tracing.mdx | 2 +- src/content/docs/agents/tools/ai-search.mdx | 2 +- .../docs/agents/tools/codemode/browser.mdx | 2 +- src/content/docs/agents/tools/mcp.mdx | 2 +- src/content/docs/agents/tools/sandbox.mdx | 2 +- .../docs/ai-search/agent-sdks/agents-sdk.mdx | 2 +- .../human-in-the-loop-knowledge-base.mdx | 2 +- .../how-to/talk-to-your-knowledge-base.mdx | 8 +- .../docs/workers/demos/chatgpt-app.mdx | 2 +- .../workflows/get-started/durable-agents.mdx | 2 +- .../agents/common-integration-patterns.mdx | 2 +- src/content/partials/agents/next-steps.mdx | 20 +-- 83 files changed, 353 insertions(+), 334 deletions(-) rename src/content/docs/agents/{runtime/execution => agent-class}/agent-tools.mdx (99%) rename src/content/docs/agents/{runtime/lifecycle => agent-class}/callable-methods.mdx (99%) rename src/content/docs/agents/{runtime/communication => agent-class/communication-channels}/chat-sdk.mdx (100%) rename src/content/docs/agents/{ => agent-class}/communication-channels/chat/autonomous-responses.mdx (96%) rename src/content/docs/agents/{ => agent-class}/communication-channels/chat/chat-agents.mdx (99%) rename src/content/docs/agents/{ => agent-class}/communication-channels/chat/client-sdk.mdx (97%) rename src/content/docs/agents/{ => agent-class}/communication-channels/chat/index.mdx (100%) rename src/content/docs/agents/{ => agent-class}/communication-channels/email.mdx (100%) rename src/content/docs/agents/{ => agent-class}/communication-channels/index.mdx (95%) rename src/content/docs/agents/{ => agent-class}/communication-channels/slack.mdx (100%) rename src/content/docs/agents/{ => agent-class}/communication-channels/voice.mdx (100%) rename src/content/docs/agents/{ => agent-class}/communication-channels/webhooks/index.mdx (98%) rename src/content/docs/agents/{ => agent-class}/communication-channels/webhooks/push-notifications.mdx (98%) rename src/content/docs/agents/{runtime/execution => agent-class}/durable-execution.mdx (96%) rename src/content/docs/agents/{runtime/communication => agent-class}/http-sse.mdx (92%) rename src/content/docs/agents/{runtime/agents-api.mdx => agent-class/index.mdx} (84%) rename src/content/docs/agents/{runtime/lifecycle/agent-class.mdx => agent-class/internals.mdx} (97%) rename src/content/docs/agents/{runtime/communication => agent-class}/protocol-messages.mdx (89%) rename src/content/docs/agents/{runtime/execution => agent-class}/queue-tasks.mdx (94%) rename src/content/docs/agents/{runtime/communication => agent-class}/readonly-connections.mdx (97%) rename src/content/docs/agents/{runtime/execution => agent-class}/retries.mdx (98%) rename src/content/docs/agents/{runtime/communication => agent-class}/routing.mdx (99%) rename src/content/docs/agents/{runtime/execution => agent-class}/run-workflows.mdx (99%) rename src/content/docs/agents/{runtime/execution => agent-class}/schedule-tasks.mdx (98%) rename src/content/docs/agents/{runtime/lifecycle => agent-class}/state.mdx (95%) rename src/content/docs/agents/{runtime/execution => agent-class}/sub-agents.mdx (95%) rename src/content/docs/agents/{runtime/operations => agent-class}/using-ai-models.mdx (92%) rename src/content/docs/agents/{runtime/communication => agent-class}/websockets.mdx (97%) delete mode 100644 src/content/docs/agents/runtime/communication/index.mdx diff --git a/public/__redirects b/public/__redirects index 3a2dc01fa39..fae3f3de5b3 100644 --- a/public/__redirects +++ b/public/__redirects @@ -240,69 +240,98 @@ /aegis/analytics/ /smart-shield/configuration/dedicated-egress-ips/ips-utilization/ 301 # agents +/agents/communication-channels/ /agents/agent-class/communication-channels/ 301 +/agents/communication-channels/chat/ /agents/agent-class/communication-channels/chat/ 301 +/agents/communication-channels/chat/autonomous-responses/ /agents/agent-class/communication-channels/chat/autonomous-responses/ 301 +/agents/communication-channels/chat/chat-agents/ /agents/agent-class/communication-channels/chat/chat-agents/ 301 +/agents/communication-channels/chat/client-sdk/ /agents/agent-class/communication-channels/chat/client-sdk/ 301 +/agents/communication-channels/email/ /agents/agent-class/communication-channels/email/ 301 +/agents/communication-channels/slack/ /agents/agent-class/communication-channels/slack/ 301 +/agents/communication-channels/voice/ /agents/agent-class/communication-channels/voice/ 301 +/agents/communication-channels/webhooks/ /agents/agent-class/communication-channels/webhooks/ 301 +/agents/communication-channels/webhooks/push-notifications/ /agents/agent-class/communication-channels/webhooks/push-notifications/ 301 +/agents/runtime/agents-api/ /agents/agent-class/ 301 +/agents/runtime/communication/ /agents/agent-class/ 301 +/agents/runtime/communication/chat-sdk/ /agents/agent-class/communication-channels/chat-sdk/ 301 +/agents/runtime/communication/http-sse/ /agents/agent-class/http-sse/ 301 +/agents/runtime/communication/protocol-messages/ /agents/agent-class/protocol-messages/ 301 +/agents/runtime/communication/readonly-connections/ /agents/agent-class/readonly-connections/ 301 +/agents/runtime/communication/routing/ /agents/agent-class/routing/ 301 +/agents/runtime/communication/websockets/ /agents/agent-class/websockets/ 301 +/agents/runtime/execution/agent-tools/ /agents/agent-class/agent-tools/ 301 +/agents/runtime/execution/durable-execution/ /agents/agent-class/durable-execution/ 301 +/agents/runtime/execution/queue-tasks/ /agents/agent-class/queue-tasks/ 301 +/agents/runtime/execution/retries/ /agents/agent-class/retries/ 301 +/agents/runtime/execution/run-workflows/ /agents/agent-class/run-workflows/ 301 +/agents/runtime/execution/schedule-tasks/ /agents/agent-class/schedule-tasks/ 301 +/agents/runtime/execution/sub-agents/ /agents/agent-class/sub-agents/ 301 +/agents/runtime/lifecycle/agent-class/ /agents/agent-class/internals/ 301 +/agents/runtime/lifecycle/callable-methods/ /agents/agent-class/callable-methods/ 301 +/agents/runtime/lifecycle/state/ /agents/agent-class/state/ 301 +/agents/runtime/operations/using-ai-models/ /agents/agent-class/using-ai-models/ 301 /agents/api-reference/think/ /agents/harnesses/think/ 301 /agents/think/ /agents/harnesses/think/ 301 /agents/build/prompts/ /workers/get-started/prompting/ 301 /agents/getting-started/build-a-chat-agent/ /agents/examples/chat-agent/ 301 /agents/examples/browse-the-web/ /agents/tools/browser/ 301 -/agents/examples/manage-and-sync-state/ /agents/runtime/lifecycle/state/ 301 +/agents/examples/manage-and-sync-state/ /agents/agent-class/state/ 301 /agents/examples/rag/ /agents/tools/ai-search/ 301 /agents/tools/rag/ /agents/tools/ai-search/ 301 -/agents/examples/run-workflows/ /agents/runtime/execution/run-workflows/ 301 -/agents/examples/schedule-tasks/ /agents/runtime/execution/schedule-tasks/ 301 -/agents/examples/using-ai-models/ /agents/runtime/operations/using-ai-models/ 301 -/agents/examples/websockets/ /agents/runtime/communication/websockets/ 301 -/agents/examples/sdk/ /agents/runtime/agents-api/ 301 +/agents/examples/run-workflows/ /agents/agent-class/run-workflows/ 301 +/agents/examples/schedule-tasks/ /agents/agent-class/schedule-tasks/ 301 +/agents/examples/using-ai-models/ /agents/agent-class/using-ai-models/ 301 +/agents/examples/websockets/ /agents/agent-class/websockets/ 301 +/agents/examples/sdk/ /agents/agent-class/ 301 /agents/examples/build-mcp-server/ /agents/model-context-protocol/guides/remote-mcp-server/ 301 /agents/api-reference/build-mcp-server/ /agents/model-context-protocol/guides/remote-mcp-server/ 301 -/agents/api-reference/sdk/ /agents/runtime/agents-api/ 301 +/agents/api-reference/sdk/ /agents/agent-class/ 301 /agents/guides/build-mcp-server/ /agents/model-context-protocol/guides/remote-mcp-server/ 301 /agents/capabilities/mcp-server/ /agents/model-context-protocol/ 301 /agents/model-context-protocol/mcp-agent-api/ /agents/model-context-protocol/apis/agent-api/ 301 /agents/model-context-protocol/mcp-client-api/ /agents/model-context-protocol/apis/client-api/ 301 /agents/model-context-protocol/mcp-handler-api/ /agents/model-context-protocol/apis/handler-api/ 301 -/agents/api-reference/calling-agents/ /agents/runtime/communication/routing/ 301 +/agents/api-reference/calling-agents/ /agents/agent-class/routing/ 301 /agents/community-mcp-server/ /agents/model-context-protocol/cloudflare/servers-for-cloudflare/community-mcp-server/ 301 /agents/patterns/ /agents/concepts/agentic-patterns/ 301 /agents/concepts/patterns/ /agents/concepts/agentic-patterns/ 301 /agents/concepts/anthropic-agent-patterns/ /agents/concepts/agentic-patterns/ 301 /agents/concepts/human-in-the-loop/ /agents/concepts/agentic-patterns/human-in-the-loop/ 301 /agents/concepts/long-running-agents/ /agents/concepts/agentic-patterns/long-running-agents/ 301 -/agents/api-reference/agents-api/ /agents/runtime/agents-api/ 301 +/agents/api-reference/agents-api/ /agents/agent-class/ 301 /agents/api-reference/ /agents/runtime/ 301 -/agents/api-reference/chat-sdk/ /agents/runtime/communication/chat-sdk/ 301 -/agents/runtime/execution/chat-sdk/ /agents/runtime/communication/chat-sdk/ 301 -/agents/api-reference/agent-tools/ /agents/runtime/execution/agent-tools/ 301 +/agents/api-reference/chat-sdk/ /agents/agent-class/communication-channels/chat-sdk/ 301 +/agents/runtime/execution/chat-sdk/ /agents/agent-class/communication-channels/chat-sdk/ 301 +/agents/api-reference/agent-tools/ /agents/agent-class/agent-tools/ 301 /agents/api-reference/browse-the-web/ /agents/tools/browser/ 301 -/agents/api-reference/callable-methods/ /agents/runtime/lifecycle/callable-methods/ 301 -/agents/api-reference/chat-agents/ /agents/communication-channels/chat/chat-agents/ 301 -/agents/api-reference/client-sdk/ /agents/communication-channels/chat/client-sdk/ 301 +/agents/api-reference/callable-methods/ /agents/agent-class/callable-methods/ 301 +/agents/api-reference/chat-agents/ /agents/agent-class/communication-channels/chat/chat-agents/ 301 +/agents/api-reference/client-sdk/ /agents/agent-class/communication-channels/chat/client-sdk/ 301 /agents/api-reference/codemode/ /agents/tools/codemode/api-reference/ 301 /agents/api-reference/configuration/ /agents/runtime/operations/configuration/ 301 -/agents/api-reference/durable-execution/ /agents/runtime/execution/durable-execution/ 301 -/agents/api-reference/email/ /agents/communication-channels/email/ 301 +/agents/api-reference/durable-execution/ /agents/agent-class/durable-execution/ 301 +/agents/api-reference/email/ /agents/agent-class/communication-channels/email/ 301 /agents/api-reference/get-current-agent/ /agents/runtime/lifecycle/get-current-agent/ 301 -/agents/api-reference/http-sse/ /agents/runtime/communication/http-sse/ 301 +/agents/api-reference/http-sse/ /agents/agent-class/http-sse/ 301 /agents/api-reference/mcp-agent-api/ /agents/model-context-protocol/apis/agent-api/ 301 /agents/api-reference/mcp-client-api/ /agents/model-context-protocol/apis/client-api/ 301 /agents/api-reference/mcp-handler-api/ /agents/model-context-protocol/apis/handler-api/ 301 /agents/api-reference/observability/ /agents/runtime/operations/observability/ 301 -/agents/api-reference/protocol-messages/ /agents/runtime/communication/protocol-messages/ 301 -/agents/api-reference/queue-tasks/ /agents/runtime/execution/queue-tasks/ 301 +/agents/api-reference/protocol-messages/ /agents/agent-class/protocol-messages/ 301 +/agents/api-reference/queue-tasks/ /agents/agent-class/queue-tasks/ 301 /agents/api-reference/rag/ /agents/tools/ai-search/ 301 -/agents/api-reference/readonly-connections/ /agents/runtime/communication/readonly-connections/ 301 -/agents/api-reference/retries/ /agents/runtime/execution/retries/ 301 -/agents/api-reference/routing/ /agents/runtime/communication/routing/ 301 -/agents/api-reference/run-workflows/ /agents/runtime/execution/run-workflows/ 301 -/agents/api-reference/schedule-tasks/ /agents/runtime/execution/schedule-tasks/ 301 +/agents/api-reference/readonly-connections/ /agents/agent-class/readonly-connections/ 301 +/agents/api-reference/retries/ /agents/agent-class/retries/ 301 +/agents/api-reference/routing/ /agents/agent-class/routing/ 301 +/agents/api-reference/run-workflows/ /agents/agent-class/run-workflows/ 301 +/agents/api-reference/schedule-tasks/ /agents/agent-class/schedule-tasks/ 301 /agents/api-reference/sessions/ /agents/runtime/lifecycle/sessions/ 301 -/agents/api-reference/store-and-sync-state/ /agents/runtime/lifecycle/state/ 301 -/agents/api-reference/sub-agents/ /agents/runtime/execution/sub-agents/ 301 -/agents/api-reference/using-ai-models/ /agents/runtime/operations/using-ai-models/ 301 -/agents/api-reference/voice/ /agents/communication-channels/voice/ 301 -/agents/api-reference/websockets/ /agents/runtime/communication/websockets/ 301 +/agents/api-reference/store-and-sync-state/ /agents/agent-class/state/ 301 +/agents/api-reference/sub-agents/ /agents/agent-class/sub-agents/ 301 +/agents/api-reference/using-ai-models/ /agents/agent-class/using-ai-models/ 301 +/agents/api-reference/voice/ /agents/agent-class/communication-channels/voice/ 301 +/agents/api-reference/websockets/ /agents/agent-class/websockets/ 301 /agents/guides/anthropic-agent-patterns/ /agents/concepts/agentic-patterns/ 301 -/agents/guides/autonomous-responses/ /agents/communication-channels/chat/autonomous-responses/ 301 +/agents/guides/autonomous-responses/ /agents/agent-class/communication-channels/chat/autonomous-responses/ 301 /agents/guides/build-a-voice-agent/ /agents/examples/voice-agent/ 301 /agents/guides/build-mcp-client/ /agents/model-context-protocol/guides/build-mcp-client/ 301 /agents/guides/chatgpt-app/ /workers/demos/chatgpt-app/ 301 @@ -311,12 +340,12 @@ /agents/guides/cross-domain-authentication/ /agents/runtime/operations/cross-domain-authentication/ 301 /agents/guides/human-in-the-loop/ /agents/concepts/agentic-patterns/human-in-the-loop/ 301 /agents/guides/oauth-mcp-client/ /agents/model-context-protocol/guides/oauth-mcp-client/ 301 -/agents/guides/push-notifications/ /agents/communication-channels/webhooks/push-notifications/ 301 +/agents/guides/push-notifications/ /agents/agent-class/communication-channels/webhooks/push-notifications/ 301 /agents/guides/remote-mcp-server/ /agents/model-context-protocol/guides/remote-mcp-server/ 301 /agents/guides/securing-mcp-server/ /agents/model-context-protocol/guides/securing-mcp-server/ 301 /agents/guides/slack-agent/ /agents/examples/slack-agent/ 301 /agents/guides/test-remote-mcp-server/ /agents/model-context-protocol/guides/test-remote-mcp-server/ 301 -/agents/guides/webhooks/ /agents/communication-channels/webhooks/ 301 +/agents/guides/webhooks/ /agents/agent-class/communication-channels/webhooks/ 301 /agents/model-context-protocol/authorization/ /agents/model-context-protocol/protocol/authorization/ 301 /agents/model-context-protocol/protocol/codemode/ /agents/tools/codemode/ 301 /agents/model-context-protocol/codemode/ /agents/model-context-protocol/guides/build-codemode-mcp-server/ 301 @@ -335,23 +364,23 @@ /agents/agentic-payments/x402/pay-with-tool-plugins/ /agents/tools/payments/x402/pay-with-tool-plugins/ 301 /agents/agentic-payments/mpp/ /agents/tools/payments/mpp/ 301 /agents/guides/ /agents/model-context-protocol/guides/ 301 -/agents/concepts/agent-class/ /agents/runtime/lifecycle/agent-class/ 301 +/agents/concepts/agent-class/ /agents/agent-class/internals/ 301 /agents/concepts/memory/ /agents/concepts/conversation-state-and-memory/ 301 /agents/getting-started/prompting/ /workers/get-started/prompting/ 301 /agents/platform/prompting/ /workers/get-started/prompting/ 301 /agents/platform/prompt.txt/ /workers/prompt.txt 301 -/agents/channels/chat/ /agents/communication-channels/chat/ 301 -/agents/channels/chat/chat-agents/ /agents/communication-channels/chat/chat-agents/ 301 -/agents/channels/chat/client-sdk/ /agents/communication-channels/chat/client-sdk/ 301 -/agents/channels/chat/autonomous-responses/ /agents/communication-channels/chat/autonomous-responses/ 301 +/agents/channels/chat/ /agents/agent-class/communication-channels/chat/ 301 +/agents/channels/chat/chat-agents/ /agents/agent-class/communication-channels/chat/chat-agents/ 301 +/agents/channels/chat/client-sdk/ /agents/agent-class/communication-channels/chat/client-sdk/ 301 +/agents/channels/chat/autonomous-responses/ /agents/agent-class/communication-channels/chat/autonomous-responses/ 301 /agents/channels/chat/chatgpt-app/ /workers/demos/chatgpt-app/ 301 -/agents/channels/voice/ /agents/communication-channels/voice/ 301 +/agents/channels/voice/ /agents/agent-class/communication-channels/voice/ 301 /agents/channels/voice/build-a-voice-agent/ /agents/examples/voice-agent/ 301 -/agents/channels/email/ /agents/communication-channels/email/ 301 -/agents/channels/slack/ /agents/communication-channels/slack/ 301 +/agents/channels/email/ /agents/agent-class/communication-channels/email/ 301 +/agents/channels/slack/ /agents/agent-class/communication-channels/slack/ 301 /agents/channels/slack/slack-agent/ /agents/examples/slack-agent/ 301 -/agents/channels/webhooks/ /agents/communication-channels/webhooks/ 301 -/agents/channels/webhooks/push-notifications/ /agents/communication-channels/webhooks/push-notifications/ 301 +/agents/channels/webhooks/ /agents/agent-class/communication-channels/webhooks/ 301 +/agents/channels/webhooks/push-notifications/ /agents/agent-class/communication-channels/webhooks/push-notifications/ 301 /agents/communication-channels/voice/build-a-voice-agent/ /agents/examples/voice-agent/ 301 /agents/communication-channels/slack/slack-agent/ /agents/examples/slack-agent/ 301 /agents/tools/browser/browse-the-web/ /agents/tools/browser/ 301 diff --git a/src/components/cf/AgentsPlatformDiagram.astro b/src/components/cf/AgentsPlatformDiagram.astro index c8a45711807..44394f8359f 100644 --- a/src/components/cf/AgentsPlatformDiagram.astro +++ b/src/components/cf/AgentsPlatformDiagram.astro @@ -3,37 +3,37 @@ const channels = [ { type: "Chat", name: "AIChatAgent", - href: "/agents/communication-channels/chat/", + href: "/agents/agent-class/communication-channels/chat/", }, { type: "Email", name: "onEmail", - href: "/agents/communication-channels/email/", + href: "/agents/agent-class/communication-channels/email/", }, { type: "Voice", name: "withVoice", - href: "/agents/communication-channels/voice/", + href: "/agents/agent-class/communication-channels/voice/", }, { type: "Slack", name: "Events", - href: "/agents/communication-channels/slack/", + href: "/agents/agent-class/communication-channels/slack/", }, { type: "Webhook", name: "HTTP", - href: "/agents/communication-channels/webhooks/", + href: "/agents/agent-class/communication-channels/webhooks/", }, ]; const runtime = [ - { label: "State", href: "/agents/runtime/lifecycle/state/" }, + { label: "State", href: "/agents/agent-class/state/" }, { label: "Sessions", href: "/agents/runtime/lifecycle/sessions/" }, - { label: "Routing", href: "/agents/runtime/communication/routing/" }, - { label: "WebSockets", href: "/agents/runtime/communication/websockets/" }, - { label: "Scheduling", href: "/agents/runtime/execution/schedule-tasks/" }, - { label: "Fibers", href: "/agents/runtime/execution/durable-execution/" }, + { label: "Routing", href: "/agents/agent-class/routing/" }, + { label: "WebSockets", href: "/agents/agent-class/websockets/" }, + { label: "Scheduling", href: "/agents/agent-class/schedule-tasks/" }, + { label: "Fibers", href: "/agents/agent-class/durable-execution/" }, ]; const tools = [ @@ -95,7 +95,7 @@ const tools = [
Pi harness - Build-your-own agent + Build-your-own agent
@@ -106,7 +106,7 @@ const tools = [ Durable identity, state, connections, scheduling, and recovery.

- + Agent class
diff --git a/src/content/changelog/agents/2025-03-18-npm-i-agents.mdx b/src/content/changelog/agents/2025-03-18-npm-i-agents.mdx index eaead553cf1..3bd3242dead 100644 --- a/src/content/changelog/agents/2025-03-18-npm-i-agents.mdx +++ b/src/content/changelog/agents/2025-03-18-npm-i-agents.mdx @@ -67,7 +67,7 @@ export default { #### Call Agent methods from your client code -We've added a new [`@unstable_callable()`](/agents/runtime/agents-api/) decorator for defining methods that can be called directly from clients. This allows you call methods from within your client code: you can call methods (with arguments) and get native JavaScript objects back. +We've added a new [`@unstable_callable()`](/agents/agent-class/) decorator for defining methods that can be called directly from clients. This allows you call methods from within your client code: you can call methods (with arguments) and get native JavaScript objects back. @@ -127,8 +127,8 @@ You can use the starter as a template for your own Agents projects: open up `src We've heard your feedback on the Agents SDK documentation, and we're shipping more API reference material and usage examples, including: - Expanded [API reference documentation](/agents/runtime/), covering the methods and properties exposed by the Agents SDK, as well as more usage examples. -- More [Client API](/agents/runtime/agents-api/#client-api) documentation that documents `useAgent`, `useAgentChat` and the new `@unstable_callable` RPC decorator exposed by the SDK. -- New documentation on how to [route requests to agents](/agents/runtime/communication/routing/) and (optionally) authenticate clients before they connect to your Agents. +- More [Client API](/agents/agent-class/#client-api) documentation that documents `useAgent`, `useAgentChat` and the new `@unstable_callable` RPC decorator exposed by the SDK. +- New documentation on how to [route requests to agents](/agents/agent-class/routing/) and (optionally) authenticate clients before they connect to your Agents. Note that the Agents SDK is continually growing: the type definitions included in the SDK will always include the latest APIs exposed by the `agents` package. diff --git a/src/content/changelog/agents/2025-04-07-mcp-servers-agents-sdk-updates.mdx b/src/content/changelog/agents/2025-04-07-mcp-servers-agents-sdk-updates.mdx index d2c50c347ac..1ef97d617d7 100644 --- a/src/content/changelog/agents/2025-04-07-mcp-servers-agents-sdk-updates.mdx +++ b/src/content/changelog/agents/2025-04-07-mcp-servers-agents-sdk-updates.mdx @@ -52,7 +52,7 @@ We've made a number of improvements to the [Agents SDK](/agents/), including: - Support for building MCP servers with the new `MCPAgent` class. - The ability to export the current agent, request and WebSocket connection context using `import { context } from "agents"`, allowing you to minimize or avoid direct dependency injection when calling tools. - Fixed a bug that prevented query parameters from being sent to the Agent server from the `useAgent` React hook. -- Automatically converting the `agent` name in `useAgent` or `useAgentChat` to kebab-case to ensure it matches the naming convention expected by [`routeAgentRequest`](/agents/runtime/communication/routing/). +- Automatically converting the `agent` name in `useAgent` or `useAgentChat` to kebab-case to ensure it matches the naming convention expected by [`routeAgentRequest`](/agents/agent-class/routing/). To install or update the Agents SDK, run `npm i agents@latest` in an existing project, or explore the `agents-starter` project: diff --git a/src/content/changelog/agents/2025-11-26-agents-resumable-streaming.mdx b/src/content/changelog/agents/2025-11-26-agents-resumable-streaming.mdx index 2416eca0da8..560b2eeac6d 100644 --- a/src/content/changelog/agents/2025-11-26-agents-resumable-streaming.mdx +++ b/src/content/changelog/agents/2025-11-26-agents-resumable-streaming.mdx @@ -24,7 +24,7 @@ Streams are maintained across page refreshes, broken connections, and syncing ac ### Other improvements - Default JSON schema validator added to MCP client -- [Schedules](https://developers.cloudflare.com/agents/runtime/execution/schedule-tasks/) can now safely destroy the agent +- [Schedules](https://developers.cloudflare.com/agents/agent-class/schedule-tasks/) can now safely destroy the agent ### MCP client API improvements @@ -60,7 +60,7 @@ MCP discovery fetches the available tools, prompts, and resources from an MCP se ### Bug fixes -- Fixed a bug where [schedules](https://developers.cloudflare.com/agents/runtime/execution/schedule-tasks/) meant to fire immediately with this.schedule(0, ...) or `this.schedule(new Date(), ...)` would not fire +- Fixed a bug where [schedules](https://developers.cloudflare.com/agents/agent-class/schedule-tasks/) meant to fire immediately with this.schedule(0, ...) or `this.schedule(new Date(), ...)` would not fire - Fixed an issue where schedules that took longer than 30 seconds would occasionally time out - Fixed SSE transport now properly forwards session IDs and request headers - Fixed AI SDK stream events conversion to UIMessageStreamPart diff --git a/src/content/changelog/agents/2026-02-03-agents-workflows-integration.mdx b/src/content/changelog/agents/2026-02-03-agents-workflows-integration.mdx index 501893ccad7..7fd6157f3c0 100644 --- a/src/content/changelog/agents/2026-02-03-agents-workflows-integration.mdx +++ b/src/content/changelog/agents/2026-02-03-agents-workflows-integration.mdx @@ -175,4 +175,4 @@ To update to the latest version: npm i agents@latest ``` -For the complete Workflows API reference and patterns, see [Run Workflows](/agents/runtime/execution/run-workflows/). +For the complete Workflows API reference and patterns, see [Run Workflows](/agents/agent-class/run-workflows/). diff --git a/src/content/changelog/agents/2026-02-17-agents-sdk-v0.5.0.mdx b/src/content/changelog/agents/2026-02-17-agents-sdk-v0.5.0.mdx index 8ecfba40a59..01093055962 100644 --- a/src/content/changelog/agents/2026-02-17-agents-sdk-v0.5.0.mdx +++ b/src/content/changelog/agents/2026-02-17-agents-sdk-v0.5.0.mdx @@ -73,7 +73,7 @@ class MyAgent extends Agent { Connections with protocol messages disabled still fully participate in RPC and regular messaging. Use `isConnectionProtocolEnabled(connection)` to check a connection's status at any time. The flag persists across Durable Object hibernation. -See [Protocol messages](/agents/runtime/communication/protocol-messages/) for full documentation. +See [Protocol messages](/agents/agent-class/protocol-messages/) for full documentation. ## `@cloudflare/ai-chat` v0.1.0 @@ -81,7 +81,7 @@ The first stable release of `@cloudflare/ai-chat` ships alongside this release w Key new features: -- **Data parts** — Attach typed JSON blobs (`data-*`) to messages alongside text. Supports reconciliation (type+id updates in-place), append, and transient parts (ephemeral via `onData` callback). See [Data parts](/agents/communication-channels/chat/chat-agents/#data-parts). +- **Data parts** — Attach typed JSON blobs (`data-*`) to messages alongside text. Supports reconciliation (type+id updates in-place), append, and transient parts (ephemeral via `onData` callback). See [Data parts](/agents/agent-class/communication-channels/chat/chat-agents/#data-parts). - **Tool approval persistence** — The `needsApproval` approval UI now survives page refresh and DO hibernation. The streaming message is persisted to SQLite when a tool enters `approval-requested` state. - **`maxPersistedMessages`** — Cap SQLite message storage with automatic oldest-message deletion. - **`body` option on `useAgentChat`** — Send custom data with every request (static or dynamic). diff --git a/src/content/changelog/agents/2026-03-02-agents-sdk-v0.7.0.mdx b/src/content/changelog/agents/2026-03-02-agents-sdk-v0.7.0.mdx index 1f34c6070b5..0c4750f999a 100644 --- a/src/content/changelog/agents/2026-03-02-agents-sdk-v0.7.0.mdx +++ b/src/content/changelog/agents/2026-03-02-agents-sdk-v0.7.0.mdx @@ -117,7 +117,7 @@ Key details: `keepAlive()` is marked `@experimental` and may change between releases. ::: -For the full API reference and when-to-use guidance, refer to [Schedule tasks — Keeping the agent alive](/agents/runtime/execution/schedule-tasks/#keeping-the-agent-alive). +For the full API reference and when-to-use guidance, refer to [Schedule tasks — Keeping the agent alive](/agents/agent-class/schedule-tasks/#keeping-the-agent-alive). ## `waitForMcpConnections` diff --git a/src/content/changelog/agents/2026-05-13-agents-sdk-v0.12.4.mdx b/src/content/changelog/agents/2026-05-13-agents-sdk-v0.12.4.mdx index 7e7d969103c..6af6f993359 100644 --- a/src/content/changelog/agents/2026-05-13-agents-sdk-v0.12.4.mdx +++ b/src/content/changelog/agents/2026-05-13-agents-sdk-v0.12.4.mdx @@ -125,4 +125,4 @@ To update to the latest version: npm i agents@latest @cloudflare/ai-chat@latest @cloudflare/think@latest @cloudflare/voice@latest ``` -Refer to the [Agents API reference](/agents/runtime/) and [Chat agents documentation](/agents/communication-channels/chat/chat-agents/) for more information. +Refer to the [Agents API reference](/agents/runtime/) and [Chat agents documentation](/agents/agent-class/communication-channels/chat/chat-agents/) for more information. diff --git a/src/content/changelog/agents/2026-06-02-agents-sdk-v0.14.0.mdx b/src/content/changelog/agents/2026-06-02-agents-sdk-v0.14.0.mdx index 55a22f00a9d..78abe7f2913 100644 --- a/src/content/changelog/agents/2026-06-02-agents-sdk-v0.14.0.mdx +++ b/src/content/changelog/agents/2026-06-02-agents-sdk-v0.14.0.mdx @@ -160,4 +160,4 @@ To update to the latest version: -Refer to the [Agents API reference](/agents/runtime/) and [Chat agents documentation](/agents/communication-channels/chat/chat-agents/) for more information. +Refer to the [Agents API reference](/agents/runtime/) and [Chat agents documentation](/agents/agent-class/communication-channels/chat/chat-agents/) for more information. diff --git a/src/content/changelog/agents/2026-06-16-agents-sdk-v0.16.1.mdx b/src/content/changelog/agents/2026-06-16-agents-sdk-v0.16.1.mdx index daf03a0d6d5..3de364dca52 100644 --- a/src/content/changelog/agents/2026-06-16-agents-sdk-v0.16.1.mdx +++ b/src/content/changelog/agents/2026-06-16-agents-sdk-v0.16.1.mdx @@ -119,4 +119,4 @@ To update to the latest version: -Refer to the [Code Mode documentation](/agents/tools/codemode/), [Browser tools documentation](/agents/tools/browser/), [Think tools documentation](/agents/harnesses/think/tools/), and [Voice documentation](/agents/communication-channels/voice/) for more information. +Refer to the [Code Mode documentation](/agents/tools/codemode/), [Browser tools documentation](/agents/tools/browser/), [Think tools documentation](/agents/harnesses/think/tools/), and [Voice documentation](/agents/agent-class/communication-channels/voice/) for more information. diff --git a/src/content/changelog/agents/2026-09-11-voice-diagnostics-turn-metrics.mdx b/src/content/changelog/agents/2026-09-11-voice-diagnostics-turn-metrics.mdx index 57b60631e2b..01946d4df05 100644 --- a/src/content/changelog/agents/2026-09-11-voice-diagnostics-turn-metrics.mdx +++ b/src/content/changelog/agents/2026-09-11-voice-diagnostics-turn-metrics.mdx @@ -69,4 +69,4 @@ Install the release with a compatible Agents SDK version: -Refer to the [Voice pipeline metrics](/agents/communication-channels/voice/#pipeline-metrics) and [Voice Agent example](https://github.com/cloudflare/agents/tree/main/examples/voice-agent) to get started. +Refer to the [Voice pipeline metrics](/agents/agent-class/communication-channels/voice/#pipeline-metrics) and [Voice Agent example](https://github.com/cloudflare/agents/tree/main/examples/voice-agent) to get started. diff --git a/src/content/docs/agents/runtime/execution/agent-tools.mdx b/src/content/docs/agents/agent-class/agent-tools.mdx similarity index 99% rename from src/content/docs/agents/runtime/execution/agent-tools.mdx rename to src/content/docs/agents/agent-class/agent-tools.mdx index 05fefda79f8..a60435e0d39 100644 --- a/src/content/docs/agents/runtime/execution/agent-tools.mdx +++ b/src/content/docs/agents/agent-class/agent-tools.mdx @@ -3,7 +3,7 @@ title: Agents as tools description: Run Think and AIChatAgent sub-agents as retained, streaming tools from a parent agent. pcx_content_type: reference sidebar: - order: 16 + order: 15 products: - agents --- @@ -510,12 +510,12 @@ Raw `diagnostics_channel` subscribers should use the channel name `agents:agent_ diff --git a/src/content/docs/agents/runtime/lifecycle/callable-methods.mdx b/src/content/docs/agents/agent-class/callable-methods.mdx similarity index 99% rename from src/content/docs/agents/runtime/lifecycle/callable-methods.mdx rename to src/content/docs/agents/agent-class/callable-methods.mdx index 4a398a7706a..a4037002264 100644 --- a/src/content/docs/agents/runtime/lifecycle/callable-methods.mdx +++ b/src/content/docs/agents/agent-class/callable-methods.mdx @@ -3,7 +3,7 @@ title: Callable methods description: Expose Agent methods to external clients over WebSocket RPC using the @callable() decorator. pcx_content_type: concept sidebar: - order: 6 + order: 3 products: - agents --- @@ -743,18 +743,18 @@ Do not set `"experimentalDecorators": true` in your `tsconfig.json`. The Agents diff --git a/src/content/docs/agents/runtime/communication/chat-sdk.mdx b/src/content/docs/agents/agent-class/communication-channels/chat-sdk.mdx similarity index 100% rename from src/content/docs/agents/runtime/communication/chat-sdk.mdx rename to src/content/docs/agents/agent-class/communication-channels/chat-sdk.mdx diff --git a/src/content/docs/agents/communication-channels/chat/autonomous-responses.mdx b/src/content/docs/agents/agent-class/communication-channels/chat/autonomous-responses.mdx similarity index 96% rename from src/content/docs/agents/communication-channels/chat/autonomous-responses.mdx rename to src/content/docs/agents/agent-class/communication-channels/chat/autonomous-responses.mdx index 69eef060c82..52768f99752 100644 --- a/src/content/docs/agents/communication-channels/chat/autonomous-responses.mdx +++ b/src/content/docs/agents/agent-class/communication-channels/chat/autonomous-responses.mdx @@ -66,7 +66,7 @@ return Response.json({ `submitMessages()` stores pending work first and appends the messages to the conversation Session only when the submission starts executing. It accepts serializable `UIMessage[]` values, not the function form supported by `saveMessages((messages) => ...)`. -Use [`startFiber()`](/agents/runtime/execution/durable-execution/#startfiber) outside Think when the durable unit is a surrounding application job, such as accepting a webhook once, restoring provider state, posting a visible reply, and recording recovery policy. `submitMessages()` owns Think's conversation admission; managed fibers own external side effects around that turn. +Use [`startFiber()`](/agents/agent-class/durable-execution/#startfiber) outside Think when the durable unit is a surrounding application job, such as accepting a webhook once, restoring provider state, posting a visible reply, and recording recovery policy. `submitMessages()` owns Think's conversation admission; managed fibers own external side effects around that turn. For the full Think API, refer to [`submitMessages()`](/agents/harnesses/think/programmatic-submissions/#submitmessages). ### When to use `saveMessages` vs `onChatResponse` @@ -152,7 +152,7 @@ export class DigestAgent extends AIChatAgent { -The function form of `saveMessages` — `saveMessages((messages) => [...])` — reads the latest persisted messages at execution time. This avoids stale baselines when multiple calls queue up (for example, rapid webhook arrivals). Refer to [Schedule tasks](/agents/runtime/execution/schedule-tasks/) for more on `schedule()` and cron syntax. +The function form of `saveMessages` — `saveMessages((messages) => [...])` — reads the latest persisted messages at execution time. This avoids stale baselines when multiple calls queue up (for example, rapid webhook arrivals). Refer to [Schedule tasks](/agents/agent-class/schedule-tasks/) for more on `schedule()` and cron syntax. ### Processing a queue @@ -528,24 +528,24 @@ Use `cancelFiber(fiberId)` when the durable unit was accepted with `startFiber() diff --git a/src/content/docs/agents/communication-channels/chat/chat-agents.mdx b/src/content/docs/agents/agent-class/communication-channels/chat/chat-agents.mdx similarity index 99% rename from src/content/docs/agents/communication-channels/chat/chat-agents.mdx rename to src/content/docs/agents/agent-class/communication-channels/chat/chat-agents.mdx index 9d7cb6d392c..7484f56c58b 100644 --- a/src/content/docs/agents/communication-channels/chat/chat-agents.mdx +++ b/src/content/docs/agents/agent-class/communication-channels/chat/chat-agents.mdx @@ -579,7 +579,7 @@ Use `abortRequest()` when you know the request ID. Use `abortAllRequests()` for Automatic stream resumption (the `resume` option on `useAgentChat`) is **client reconnect recovery** — it resumes an active stream when a client disconnects and reconnects. It does not cover Durable Object eviction. If the Worker process or Durable Object is evicted while the model call is in flight, the stream itself is gone. Durable chat recovery handles that case. -A mid-stream Durable Object eviction permanently severs the LLM connection. Durable recovery wraps every `AIChatAgent` and [`Think`](/agents/harnesses/think/) chat turn in a [`runFiber()`](/agents/runtime/execution/durable-execution/). The fiber provides automatic `keepAlive` during streaming and a recovery hook on restart. +A mid-stream Durable Object eviction permanently severs the LLM connection. Durable recovery wraps every `AIChatAgent` and [`Think`](/agents/harnesses/think/) chat turn in a [`runFiber()`](/agents/agent-class/durable-execution/). The fiber provides automatic `keepAlive` during streaming and a recovery hook on restart. The fiber row survives in SQLite after an eviction. On the next activation, the framework detects the interrupted fiber. It reconstructs the partial response from buffered stream chunks and calls `onChatRecovery`. @@ -833,7 +833,7 @@ While a turn is being recovered, the agent broadcasts a `cf_agent_chat_recoverin Transcript repairs — healing orphaned tool calls (preserved as errored results rather than deleted, so the record survives and the model does not silently re-run the tool) and normalizing malformed or missing tool inputs before a provider call — are emitted on the `transcript` observability channel. -For how chat recovery fits into the broader long-running agents story, refer to [Long-running agents: Recovering interrupted LLM streams](/agents/concepts/agentic-patterns/long-running-agents/#recovering-interrupted-llm-streams). For the underlying fiber API, refer to [Durable Execution](/agents/runtime/execution/durable-execution/). +For how chat recovery fits into the broader long-running agents story, refer to [Long-running agents: Recovering interrupted LLM streams](/agents/concepts/agentic-patterns/long-running-agents/#recovering-interrupted-llm-streams). For the underlying fiber API, refer to [Durable Execution](/agents/agent-class/durable-execution/). ## Client API @@ -1616,7 +1616,7 @@ export class ChatAgent extends AIChatAgent { :::note -This section covers **in-process** subagents using the AI SDK's `ToolLoopAgent`. For **Durable Object sub-agents** with their own isolated storage and typed RPC, refer to [Sub-agents](/agents/runtime/execution/sub-agents/). To run Think or `AIChatAgent` sub-agents as retained, streaming tools, refer to [Agents as tools](/agents/runtime/execution/agent-tools/). +This section covers **in-process** subagents using the AI SDK's `ToolLoopAgent`. For **Durable Object sub-agents** with their own isolated storage and typed RPC, refer to [Sub-agents](/agents/agent-class/sub-agents/). To run Think or `AIChatAgent` sub-agents as retained, streaming tools, refer to [Agents as tools](/agents/agent-class/agent-tools/). ::: @@ -1778,7 +1778,7 @@ If you are upgrading from an earlier version, replace deprecated calls with thei @@ -1796,7 +1796,7 @@ If you are upgrading from an earlier version, replace deprecated calls with thei diff --git a/src/content/docs/agents/communication-channels/chat/client-sdk.mdx b/src/content/docs/agents/agent-class/communication-channels/chat/client-sdk.mdx similarity index 97% rename from src/content/docs/agents/communication-channels/chat/client-sdk.mdx rename to src/content/docs/agents/agent-class/communication-channels/chat/client-sdk.mdx index e061391c6b6..32340fabe03 100644 --- a/src/content/docs/agents/communication-channels/chat/client-sdk.mdx +++ b/src/content/docs/agents/agent-class/communication-channels/chat/client-sdk.mdx @@ -638,11 +638,11 @@ const transport = new WebSocketChatTransport({ -Refer to [Non-React clients](/agents/communication-channels/chat/chat-agents/#non-react-clients) for the behaviors this transport does and does not cover. +Refer to [Non-React clients](/agents/agent-class/communication-channels/chat/chat-agents/#non-react-clients) for the behaviors this transport does and does not cover. ## Agent-tool events -If your chat UI renders retained child runs from [Agents as tools](/agents/runtime/execution/agent-tools/), use `useAgentToolEvents()` alongside `useAgent()` and `useAgentChat()`. The hook subscribes to the parent connection, replays retained child timelines, and groups runs by parent tool call ID. +If your chat UI renders retained child runs from [Agents as tools](/agents/agent-class/agent-tools/), use `useAgentToolEvents()` alongside `useAgent()` and `useAgentChat()`. The hook subscribes to the parent connection, replays retained child timelines, and groups runs by parent tool call ID. @@ -661,13 +661,13 @@ const agentTools = useAgentToolEvents({ agent }); diff --git a/src/content/docs/agents/communication-channels/chat/index.mdx b/src/content/docs/agents/agent-class/communication-channels/chat/index.mdx similarity index 100% rename from src/content/docs/agents/communication-channels/chat/index.mdx rename to src/content/docs/agents/agent-class/communication-channels/chat/index.mdx diff --git a/src/content/docs/agents/communication-channels/email.mdx b/src/content/docs/agents/agent-class/communication-channels/email.mdx similarity index 100% rename from src/content/docs/agents/communication-channels/email.mdx rename to src/content/docs/agents/agent-class/communication-channels/email.mdx diff --git a/src/content/docs/agents/communication-channels/index.mdx b/src/content/docs/agents/agent-class/communication-channels/index.mdx similarity index 95% rename from src/content/docs/agents/communication-channels/index.mdx rename to src/content/docs/agents/agent-class/communication-channels/index.mdx index 935c15443eb..c90e2973369 100644 --- a/src/content/docs/agents/communication-channels/index.mdx +++ b/src/content/docs/agents/agent-class/communication-channels/index.mdx @@ -2,7 +2,7 @@ title: Communication channels pcx_content_type: navigation sidebar: - order: 3 + order: 20 group: hideIndex: true --- diff --git a/src/content/docs/agents/communication-channels/slack.mdx b/src/content/docs/agents/agent-class/communication-channels/slack.mdx similarity index 100% rename from src/content/docs/agents/communication-channels/slack.mdx rename to src/content/docs/agents/agent-class/communication-channels/slack.mdx diff --git a/src/content/docs/agents/communication-channels/voice.mdx b/src/content/docs/agents/agent-class/communication-channels/voice.mdx similarity index 100% rename from src/content/docs/agents/communication-channels/voice.mdx rename to src/content/docs/agents/agent-class/communication-channels/voice.mdx diff --git a/src/content/docs/agents/communication-channels/webhooks/index.mdx b/src/content/docs/agents/agent-class/communication-channels/webhooks/index.mdx similarity index 98% rename from src/content/docs/agents/communication-channels/webhooks/index.mdx rename to src/content/docs/agents/agent-class/communication-channels/webhooks/index.mdx index ca43097d33f..794a82189bb 100644 --- a/src/content/docs/agents/communication-channels/webhooks/index.mdx +++ b/src/content/docs/agents/agent-class/communication-channels/webhooks/index.mdx @@ -518,7 +518,7 @@ return Response.json( -If the webhook owns application side effects around a turn, such as restoring a provider thread and posting a visible reply, use [`startFiber()`](/agents/runtime/execution/durable-execution/#startfiber) around that job. Managed fibers retain status, dedupe provider retries, and let `onFiberRecovered()` or `resolveFiber()` record the app-level recovery outcome. +If the webhook owns application side effects around a turn, such as restoring a provider thread and posting a visible reply, use [`startFiber()`](/agents/agent-class/durable-execution/#startfiber) around that job. Managed fibers retain status, dedupe provider retries, and let `onFiberRecovered()` or `resolveFiber()` record the app-level recovery outcome. ### Multi-provider routing @@ -647,18 +647,18 @@ const secret = this.env.GITHUB_WEBHOOK_SECRET; diff --git a/src/content/docs/agents/communication-channels/webhooks/push-notifications.mdx b/src/content/docs/agents/agent-class/communication-channels/webhooks/push-notifications.mdx similarity index 98% rename from src/content/docs/agents/communication-channels/webhooks/push-notifications.mdx rename to src/content/docs/agents/agent-class/communication-channels/webhooks/push-notifications.mdx index 30fd5bad7ab..5b79b0a329d 100644 --- a/src/content/docs/agents/communication-channels/webhooks/push-notifications.mdx +++ b/src/content/docs/agents/agent-class/communication-channels/webhooks/push-notifications.mdx @@ -405,18 +405,18 @@ try { diff --git a/src/content/docs/agents/runtime/execution/durable-execution.mdx b/src/content/docs/agents/agent-class/durable-execution.mdx similarity index 96% rename from src/content/docs/agents/runtime/execution/durable-execution.mdx rename to src/content/docs/agents/agent-class/durable-execution.mdx index ee2ca4213a0..5d93b1eff4c 100644 --- a/src/content/docs/agents/runtime/execution/durable-execution.mdx +++ b/src/content/docs/agents/agent-class/durable-execution.mdx @@ -60,7 +60,7 @@ When eviction happens mid-work, the upstream HTTP connection (to an LLM provider `keepAlive()` reduces the chance of eviction. `runFiber()` makes eviction survivable. -For work that should run independently of the agent with per-step retries and multi-step orchestration, use [Workflows](/agents/runtime/execution/run-workflows/) instead. Fibers are for work that is part of the agent's own execution. Refer to [Long-running agents: Workflows vs agent-internal patterns](/agents/concepts/agentic-patterns/long-running-agents/#when-to-use-workflows-vs-agent-internal-patterns) for a comparison. +For work that should run independently of the agent with per-step retries and multi-step orchestration, use [Workflows](/agents/agent-class/run-workflows/) instead. Fibers are for work that is part of the agent's own execution. Refer to [Long-running agents: Workflows vs agent-internal patterns](/agents/concepts/agentic-patterns/long-running-agents/#when-to-use-workflows-vs-agent-internal-patterns) for a comparison. ## keepAlive @@ -495,7 +495,7 @@ Run an async function while keeping the DO alive. Heartbeat starts before `fn` a ## Related - [Long-running agents](/agents/concepts/agentic-patterns/long-running-agents/) — how fibers compose with schedules, plans, and async operations -- [Schedule tasks](/agents/runtime/execution/schedule-tasks/) — `keepAlive` details and the alarm system -- [Sub-agents](/agents/runtime/execution/sub-agents/) — durable execution and schedules inside sub-agents -- [Workflows](/agents/runtime/execution/run-workflows/) — durable multi-step execution outside the agent -- [Chat agents](/agents/communication-channels/chat/chat-agents/) — `chatRecovery` and `onChatRecovery` +- [Schedule tasks](/agents/agent-class/schedule-tasks/) — `keepAlive` details and the alarm system +- [Sub-agents](/agents/agent-class/sub-agents/) — durable execution and schedules inside sub-agents +- [Workflows](/agents/agent-class/run-workflows/) — durable multi-step execution outside the agent +- [Chat agents](/agents/agent-class/communication-channels/chat/chat-agents/) — `chatRecovery` and `onChatRecovery` diff --git a/src/content/docs/agents/runtime/communication/http-sse.mdx b/src/content/docs/agents/agent-class/http-sse.mdx similarity index 92% rename from src/content/docs/agents/runtime/communication/http-sse.mdx rename to src/content/docs/agents/agent-class/http-sse.mdx index 860023ce43c..f8a8daf0ced 100644 --- a/src/content/docs/agents/runtime/communication/http-sse.mdx +++ b/src/content/docs/agents/agent-class/http-sse.mdx @@ -3,7 +3,7 @@ title: HTTP and Server-Sent Events description: Handle HTTP requests and stream responses with Server-Sent Events (SSE) from Cloudflare Agents. pcx_content_type: reference sidebar: - order: 9 + order: 5 products: - agents --- @@ -146,8 +146,8 @@ export class ChatAgent extends Agent { SSE connections can be long-lived. Handle client disconnects gracefully: -- **Persist progress** — Write to [agent state](/agents/runtime/lifecycle/state/) so clients can resume -- **Use agent routing** — Clients can [reconnect to the same agent instance](/agents/runtime/communication/routing/) without session stores +- **Persist progress** — Write to [agent state](/agents/agent-class/state/) so clients can resume +- **Use agent routing** — Clients can [reconnect to the same agent instance](/agents/agent-class/routing/) without session stores - **No timeout limits** — Cloudflare Workers have no effective limit on SSE response duration @@ -190,19 +190,19 @@ export class ResumeAgent extends Agent { **Recommendation:** Use WebSockets for interactive applications. Use SSE for streaming AI responses or server-push notifications. -Refer to [WebSockets](/agents/runtime/communication/websockets/) for WebSocket documentation. +Refer to [WebSockets](/agents/agent-class/websockets/) for WebSocket documentation. ## Next steps diff --git a/src/content/docs/agents/runtime/agents-api.mdx b/src/content/docs/agents/agent-class/index.mdx similarity index 84% rename from src/content/docs/agents/runtime/agents-api.mdx rename to src/content/docs/agents/agent-class/index.mdx index bede1c78b73..bf96338ed85 100644 --- a/src/content/docs/agents/runtime/agents-api.mdx +++ b/src/content/docs/agents/agent-class/index.mdx @@ -1,9 +1,11 @@ --- -title: Agents API -description: Reference for the Agent base class, lifecycle hooks, SQL storage, and error handling in the Agents SDK. +title: Agent class +description: Reference for the Agent base class, its hooks, SQL storage, and error handling in the Agents SDK. pcx_content_type: reference sidebar: - order: 1 + order: 8 + group: + hideIndex: false products: - agents --- @@ -65,7 +67,7 @@ flowchart TD | Method | When it runs | | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `onStart(props?)` | When the instance starts, or wakes from hibernation. Receives optional [initialization props](/agents/runtime/communication/routing/#props) passed via `getAgentByName` or `routeAgentRequest`. | +| `onStart(props?)` | When the instance starts, or wakes from hibernation. Receives optional [initialization props](/agents/agent-class/routing/#props) passed via `getAgentByName` or `routeAgentRequest`. | | `onRequest(request)` | For each HTTP request to the instance | | `onConnect(connection, ctx)` | When a WebSocket connection is established | | `onMessage(connection, message)` | For each WebSocket message received | @@ -87,27 +89,27 @@ flowchart TD | Feature | Methods | Documentation | | ------------------------ | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | -| **State** | `setState()`, `onStateChanged()`, `initialState` | [Store and sync state](/agents/runtime/lifecycle/state/) | -| **Callable methods** | `@callable()` decorator | [Callable methods](/agents/runtime/lifecycle/callable-methods/) | -| **Scheduling** | `schedule()`, `scheduleEvery()`, `getScheduleById()`, `listSchedules()` | [Schedule tasks](/agents/runtime/execution/schedule-tasks/) | -| **Durable execution** | `runFiber()`, `startFiber()`, `stash()`, `onFiberRecovered()`, `keepAlive()`, `keepAliveWhile()` | [Durable execution](/agents/runtime/execution/durable-execution/) | -| **Queue** | `queue()`, `dequeue()`, `dequeueAll()`, `getQueue()` | [Queue tasks](/agents/runtime/execution/queue-tasks/) | -| **WebSockets** | `onConnect()`, `onMessage()`, `onClose()`, `broadcast()` | [WebSockets](/agents/runtime/communication/websockets/) | -| **HTTP/SSE** | `onRequest()` | [HTTP and SSE](/agents/runtime/communication/http-sse/) | -| **Email** | `onEmail()`, `replyToEmail()` | [Email routing](/agents/communication-channels/email/) | -| **Workflows** | `runWorkflow()`, `waitForApproval()` | [Run Workflows](/agents/runtime/execution/run-workflows/) | +| **State** | `setState()`, `onStateChanged()`, `initialState` | [Store and sync state](/agents/agent-class/state/) | +| **Callable methods** | `@callable()` decorator | [Callable methods](/agents/agent-class/callable-methods/) | +| **Scheduling** | `schedule()`, `scheduleEvery()`, `getScheduleById()`, `listSchedules()` | [Schedule tasks](/agents/agent-class/schedule-tasks/) | +| **Durable execution** | `runFiber()`, `startFiber()`, `stash()`, `onFiberRecovered()`, `keepAlive()`, `keepAliveWhile()` | [Durable execution](/agents/agent-class/durable-execution/) | +| **Queue** | `queue()`, `dequeue()`, `dequeueAll()`, `getQueue()` | [Queue tasks](/agents/agent-class/queue-tasks/) | +| **WebSockets** | `onConnect()`, `onMessage()`, `onClose()`, `broadcast()` | [WebSockets](/agents/agent-class/websockets/) | +| **HTTP/SSE** | `onRequest()` | [HTTP and SSE](/agents/agent-class/http-sse/) | +| **Email** | `onEmail()`, `replyToEmail()` | [Email routing](/agents/agent-class/communication-channels/email/) | +| **Workflows** | `runWorkflow()`, `waitForApproval()` | [Run Workflows](/agents/agent-class/run-workflows/) | | **MCP Client** | `addMcpServer()`, `removeMcpServer()`, `getMcpServers()` | [MCP Client API](/agents/model-context-protocol/apis/client-api/) | -| **AI Models** | Workers AI, OpenAI, Anthropic bindings | [Using AI models](/agents/runtime/operations/using-ai-models/) | -| **Protocol messages** | `shouldSendProtocolMessages()`, `isConnectionProtocolEnabled()` | [Protocol messages](/agents/runtime/communication/protocol-messages/) | +| **AI Models** | Workers AI, OpenAI, Anthropic bindings | [Using AI models](/agents/agent-class/using-ai-models/) | +| **Protocol messages** | `shouldSendProtocolMessages()`, `isConnectionProtocolEnabled()` | [Protocol messages](/agents/agent-class/protocol-messages/) | | **Context** | `getCurrentAgent()` | [getCurrentAgent()](/agents/runtime/lifecycle/get-current-agent/) | | **Tracing** | `wrapAISDK()` | [Tracing](/agents/runtime/operations/observability/tracing/) | | **Diagnostics channels** | `subscribe()`, diagnostics channels | [Diagnostics channels](/agents/runtime/operations/observability/diagnostics-channels/) | -| **Sub-agents** | `subAgent()`, `abortSubAgent()`, `deleteSubAgent()` | [Sub-agents](/agents/runtime/execution/sub-agents/) | -| **Agents as tools** | `runAgentTool()`, `clearAgentToolRuns()`, `hasAgentToolRun()` | [Agents as tools](/agents/runtime/execution/agent-tools/) | +| **Sub-agents** | `subAgent()`, `abortSubAgent()`, `deleteSubAgent()` | [Sub-agents](/agents/agent-class/sub-agents/) | +| **Agents as tools** | `runAgentTool()`, `clearAgentToolRuns()`, `hasAgentToolRun()` | [Agents as tools](/agents/agent-class/agent-tools/) | | **Agent Skills** | `skills` registry, bundled skill sources, script runners | [Agent Skills](/agents/runtime/execution/agent-skills/) | | **Sessions** | `Session.create()`, context blocks, compaction, search | [Sessions](/agents/runtime/lifecycle/sessions/) | | **Think** | `Think` base class, workspace tools, lifecycle hooks, extensions | [Think](/agents/harnesses/think/) | -| **Chat SDK** | `createChatSdkState()`, `ChatSdkStateAgent` | [Chat SDK](/agents/runtime/communication/chat-sdk/) | +| **Chat SDK** | `createChatSdkState()`, `ChatSdkStateAgent` | [Chat SDK](/agents/agent-class/communication-channels/chat-sdk/) | ## SQL API @@ -124,17 +126,17 @@ this.sql`INSERT INTO users (id, name) VALUES (${id}, ${name})`; const users = this.sql`SELECT * FROM users WHERE id = ${id}`; ``` -For state that needs to sync with clients, use the [State API](/agents/runtime/lifecycle/state/) instead. +For state that needs to sync with clients, use the [State API](/agents/agent-class/state/) instead. ## Client-side API reference | Feature | Methods | Documentation | | --------------------- | ---------------------- | ------------------------------------------------------------------------------------------- | -| **WebSocket client** | `AgentClient` | [Client SDK](/agents/communication-channels/chat/client-sdk/) | -| **HTTP client** | `agentFetch()` | [Client SDK](/agents/communication-channels/chat/client-sdk/#http-requests-with-agentfetch) | -| **React hook** | `useAgent()` | [Client SDK](/agents/communication-channels/chat/client-sdk/#react) | -| **Chat hook** | `useAgentChat()` | [Client SDK](/agents/communication-channels/chat/client-sdk/) | -| **Agent tool events** | `useAgentToolEvents()` | [Agents as tools](/agents/runtime/execution/agent-tools/#render-child-timelines-in-react) | +| **WebSocket client** | `AgentClient` | [Client SDK](/agents/agent-class/communication-channels/chat/client-sdk/) | +| **HTTP client** | `agentFetch()` | [Client SDK](/agents/agent-class/communication-channels/chat/client-sdk/#http-requests-with-agentfetch) | +| **React hook** | `useAgent()` | [Client SDK](/agents/agent-class/communication-channels/chat/client-sdk/#react) | +| **Chat hook** | `useAgentChat()` | [Client SDK](/agents/agent-class/communication-channels/chat/client-sdk/) | +| **Agent tool events** | `useAgentToolEvents()` | [Agents as tools](/agents/agent-class/agent-tools/#render-child-timelines-in-react) | Module-level helper exports include `agentTool()` from `agents/agent-tools`, which converts a Think or `AIChatAgent` subclass into an AI SDK tool definition. @@ -204,7 +206,7 @@ export default { } satisfies ExportedHandler; ``` -Refer to [Routing](/agents/runtime/communication/routing/) for custom paths, CORS, and instance naming patterns. +Refer to [Routing](/agents/agent-class/routing/) for custom paths, CORS, and instance naming patterns. ## Next steps @@ -222,7 +224,7 @@ Refer to [Routing](/agents/runtime/communication/routing/) for custom paths, COR diff --git a/src/content/docs/agents/runtime/lifecycle/agent-class.mdx b/src/content/docs/agents/agent-class/internals.mdx similarity index 97% rename from src/content/docs/agents/runtime/lifecycle/agent-class.mdx rename to src/content/docs/agents/agent-class/internals.mdx index 21d34f97c5e..27bf5913a3a 100644 --- a/src/content/docs/agents/runtime/lifecycle/agent-class.mdx +++ b/src/content/docs/agents/agent-class/internals.mdx @@ -3,7 +3,7 @@ title: Agent class internals description: Explore how the Agent class extends Durable Objects to provide state, WebSockets, scheduling, and RPC. pcx_content_type: concept sidebar: - order: 5 + order: 1 products: - agents --- @@ -210,7 +210,7 @@ class MyAgent extends Agent { } ``` -State is stored in the `cf_agents_state` SQL table. State messages are sent with `type: "cf_agent_state"` (both from the client and the server). Since `agents` provides [JS and React clients](/agents/runtime/lifecycle/state/#synchronizing-state), real-time state updates are available out of the box. +State is stored in the `cf_agents_state` SQL table. State messages are sent with `type: "cf_agent_state"` (both from the client and the server). Since `agents` provides [JS and React clients](/agents/agent-class/state/#synchronizing-state), real-time state updates are available out of the box. ### `this.sql` @@ -507,7 +507,7 @@ class MyAgent extends Agent { } ``` -`AIChatAgent` uses `keepAliveWhile` internally to keep the agent alive during streaming LLM responses. For more details, refer to [Schedule tasks — Keeping the agent alive](/agents/runtime/execution/schedule-tasks/#keeping-the-agent-alive). +`AIChatAgent` uses `keepAliveWhile` internally to keep the agent alive during streaming LLM responses. For more details, refer to [Schedule tasks — Keeping the agent alive](/agents/agent-class/schedule-tasks/#keeping-the-agent-alive). ### Routing @@ -525,7 +525,7 @@ return new Response("Not found", { status: 404 }); ## Layer 3: `AIChatAgent` -The [`AIChatAgent`](/agents/communication-channels/chat/chat-agents/) class from `@cloudflare/ai-chat` extends `Agent` with an opinionated layer for AI chat. It adds automatic message persistence to SQLite, resumable streaming, tool support (server-side, client-side, and human-in-the-loop), and a React hook (`useAgentChat`) for building chat UIs. +The [`AIChatAgent`](/agents/agent-class/communication-channels/chat/chat-agents/) class from `@cloudflare/ai-chat` extends `Agent` with an opinionated layer for AI chat. It adds automatic message persistence to SQLite, resumable streaming, tool support (server-side, client-side, and human-in-the-loop), and a React hook (`useAgentChat`) for building chat UIs. The full hierarchy is: **DurableObject** > **Server** > **Agent** > **AIChatAgent**. diff --git a/src/content/docs/agents/runtime/communication/protocol-messages.mdx b/src/content/docs/agents/agent-class/protocol-messages.mdx similarity index 89% rename from src/content/docs/agents/runtime/communication/protocol-messages.mdx rename to src/content/docs/agents/agent-class/protocol-messages.mdx index 1097bf4606a..67ed33ead38 100644 --- a/src/content/docs/agents/runtime/communication/protocol-messages.mdx +++ b/src/content/docs/agents/agent-class/protocol-messages.mdx @@ -3,7 +3,7 @@ title: Protocol messages description: Control the identity, state, and MCP protocol messages sent to WebSocket clients on Agent connect. pcx_content_type: concept sidebar: - order: 27 + order: 8 products: - agents --- @@ -24,7 +24,7 @@ On every new connection, the Agent sends three protocol messages: State and MCP messages are also broadcast to all connections whenever they change. -For most web clients this is fine — the [Client SDK](/agents/communication-channels/chat/client-sdk/) and `useAgent` hook consume these messages automatically. However, some clients cannot handle JSON text frames: +For most web clients this is fine — the [Client SDK](/agents/agent-class/communication-channels/chat/client-sdk/) and `useAgent` hook consume these messages automatically. However, some clients cannot handle JSON text frames: - **Binary-only clients** — MQTT devices, IoT sensors, custom binary protocols - **Lightweight clients** — Embedded systems with minimal WebSocket stacks @@ -173,7 +173,7 @@ An overridable hook that determines if a connection should receive protocol mess Default: returns `true` (all connections receive protocol messages). -This hook is evaluated once on connect. The result is persisted in the connection's WebSocket attachment and survives [hibernation](/agents/runtime/communication/websockets/#hibernation). +This hook is evaluated once on connect. The result is persisted in the connection's WebSocket attachment and survives [hibernation](/agents/agent-class/websockets/#hibernation). ### `isConnectionProtocolEnabled` @@ -188,18 +188,18 @@ Safe to call at any time, including after the agent wakes from hibernation. ## How it works -Protocol status is stored as an internal flag in the connection's WebSocket attachment — the same mechanism used by [readonly connections](/agents/runtime/communication/readonly-connections/). This means: +Protocol status is stored as an internal flag in the connection's WebSocket attachment — the same mechanism used by [readonly connections](/agents/agent-class/readonly-connections/). This means: - **Survives hibernation** — the flag is serialized and restored when the agent wakes up - **No cleanup needed** — connection state is automatically discarded when the connection closes - **Zero overhead** — no database tables or queries, just the connection's built-in attachment - **Safe from user code** — `connection.state` and `connection.setState()` never expose or overwrite the flag -Unlike [readonly](/agents/runtime/communication/readonly-connections/) which can be toggled dynamically with `setConnectionReadonly()`, protocol status is set once on connect and cannot be changed afterward. To change a connection's protocol status, the client must disconnect and reconnect. +Unlike [readonly](/agents/agent-class/readonly-connections/) which can be toggled dynamically with `setConnectionReadonly()`, protocol status is set once on connect and cannot be changed afterward. To change a connection's protocol status, the client must disconnect and reconnect. ## Related resources -- [Readonly connections](/agents/runtime/communication/readonly-connections/) -- [WebSockets](/agents/runtime/communication/websockets/) -- [Store and sync state](/agents/runtime/lifecycle/state/) +- [Readonly connections](/agents/agent-class/readonly-connections/) +- [WebSockets](/agents/agent-class/websockets/) +- [Store and sync state](/agents/agent-class/state/) - [MCP Client API](/agents/model-context-protocol/apis/client-api/) diff --git a/src/content/docs/agents/runtime/execution/queue-tasks.mdx b/src/content/docs/agents/agent-class/queue-tasks.mdx similarity index 94% rename from src/content/docs/agents/runtime/execution/queue-tasks.mdx rename to src/content/docs/agents/agent-class/queue-tasks.mdx index bedead7d6ac..ce6e7dfabc1 100644 --- a/src/content/docs/agents/runtime/execution/queue-tasks.mdx +++ b/src/content/docs/agents/agent-class/queue-tasks.mdx @@ -3,7 +3,7 @@ title: Queue tasks description: Add background tasks to a built-in FIFO queue for asynchronous processing within Cloudflare Agents. pcx_content_type: concept sidebar: - order: 12 + order: 10 products: - agents --- @@ -47,7 +47,7 @@ async queue( - `callback` - The name of the method to call when processing the task - `payload` - Data to pass to the callback method - `options` - Optional configuration: - - `retry` - Retry options for the callback execution. If the callback throws, it is retried with exponential backoff. Refer to [Retries](/agents/runtime/execution/retries/) for details on `RetryOptions` + - `retry` - Retry options for the callback execution. If the callback throws, it is retried with exponential backoff. Refer to [Retries](/agents/agent-class/retries/) for details on `RetryOptions` **Returns:** The unique ID of the queued task @@ -338,7 +338,7 @@ class RobustAgent extends Agent { -If no `retry` option is provided, the class-level defaults from `static options.retry` are used (3 attempts, 100ms base delay, 3s max delay). Refer to [Retries](/agents/runtime/execution/retries/) for full details. +If no `retry` option is provided, the class-level defaults from `static options.retry` are used (3 attempts, 100ms base delay, 3s max delay). Refer to [Retries](/agents/agent-class/retries/) for full details. ## Best practices @@ -353,7 +353,7 @@ If no `retry` option is provided, the class-level defaults from `static options. The queue system works with other Agent SDK features: - **State management**: Access agent state within queued callbacks. -- **Scheduling**: Combine with [`schedule()`](/agents/runtime/execution/schedule-tasks/) for time-based queue processing. +- **Scheduling**: Combine with [`schedule()`](/agents/agent-class/schedule-tasks/) for time-based queue processing. - **Context**: Queued tasks maintain the original request context. - **Database**: Uses the same database as other agent data. @@ -365,7 +365,7 @@ The queue system works with other Agent SDK features: ## Queue vs Schedule -Use **queue** when you want tasks to execute as soon as possible in order. Use [**schedule**](/agents/runtime/execution/schedule-tasks/) when you need tasks to run at specific times or on a recurring basis. +Use **queue** when you want tasks to execute as soon as possible in order. Use [**schedule**](/agents/agent-class/schedule-tasks/) when you need tasks to run at specific times or on a recurring basis. | Feature | Queue | Schedule | | ---------------- | ------------------------ | --------------------------- | @@ -377,18 +377,18 @@ Use **queue** when you want tasks to execute as soon as possible in order. Use [ diff --git a/src/content/docs/agents/runtime/communication/readonly-connections.mdx b/src/content/docs/agents/agent-class/readonly-connections.mdx similarity index 97% rename from src/content/docs/agents/runtime/communication/readonly-connections.mdx rename to src/content/docs/agents/agent-class/readonly-connections.mdx index 35e683a7e6f..7936d4b3a31 100644 --- a/src/content/docs/agents/runtime/communication/readonly-connections.mdx +++ b/src/content/docs/agents/agent-class/readonly-connections.mdx @@ -3,7 +3,7 @@ title: Readonly connections description: Restrict WebSocket clients to view-only access so they receive state updates without modifying Agent state. pcx_content_type: concept sidebar: - order: 28 + order: 7 products: - agents --- @@ -455,7 +455,7 @@ function GameComponent() { ## How it works -Readonly status is stored in the connection's WebSocket attachment, which persists through the WebSocket Hibernation API. The flag is namespaced internally so it cannot be accidentally overwritten by `connection.setState()`. The same mechanism is used by [protocol message control](/agents/runtime/communication/protocol-messages/) — both flag coexist safely in the attachment. This means: +Readonly status is stored in the connection's WebSocket attachment, which persists through the WebSocket Hibernation API. The flag is namespaced internally so it cannot be accidentally overwritten by `connection.setState()`. The same mechanism is used by [protocol message control](/agents/agent-class/protocol-messages/) — both flag coexist safely in the attachment. This means: - **Survives hibernation** — the flag is serialized and restored when the agent wakes up - **No cleanup needed** — connection state is automatically discarded when the connection closes @@ -627,7 +627,7 @@ export class AuditedAgent extends Agent { ## Related resources -- [Store and sync state](/agents/runtime/lifecycle/state/) -- [Protocol messages](/agents/runtime/communication/protocol-messages/) — suppress JSON protocol frames for binary-only clients (can be combined with readonly) -- [WebSockets](/agents/runtime/communication/websockets/) -- [Callable methods](/agents/runtime/lifecycle/callable-methods/) +- [Store and sync state](/agents/agent-class/state/) +- [Protocol messages](/agents/agent-class/protocol-messages/) — suppress JSON protocol frames for binary-only clients (can be combined with readonly) +- [WebSockets](/agents/agent-class/websockets/) +- [Callable methods](/agents/agent-class/callable-methods/) diff --git a/src/content/docs/agents/runtime/execution/retries.mdx b/src/content/docs/agents/agent-class/retries.mdx similarity index 98% rename from src/content/docs/agents/runtime/execution/retries.mdx rename to src/content/docs/agents/agent-class/retries.mdx index 3cd350f13f8..b9ed5201c2f 100644 --- a/src/content/docs/agents/runtime/execution/retries.mdx +++ b/src/content/docs/agents/agent-class/retries.mdx @@ -3,7 +3,7 @@ title: Retries description: Retry failed operations with exponential backoff and jitter using the built-in retry system in the Agents SDK. pcx_content_type: reference sidebar: - order: 13 + order: 12 products: - agents --- @@ -507,18 +507,18 @@ class MyAgent extends Agent { diff --git a/src/content/docs/agents/runtime/communication/routing.mdx b/src/content/docs/agents/agent-class/routing.mdx similarity index 99% rename from src/content/docs/agents/runtime/communication/routing.mdx rename to src/content/docs/agents/agent-class/routing.mdx index 07756e8227b..231edc4fa6a 100644 --- a/src/content/docs/agents/runtime/communication/routing.mdx +++ b/src/content/docs/agents/agent-class/routing.mdx @@ -3,7 +3,7 @@ title: Routing description: Route HTTP and WebSocket requests to Agents SDK instances using routeAgentRequest() and getAgentByName(). pcx_content_type: reference sidebar: - order: 2 + order: 4 products: - agents --- @@ -100,7 +100,7 @@ buildAgentUrl("https://app.example.com", address, { Inside an Agent, `this.selfPath` provides the required root-first identity. If the root Durable Object binding name differs from its class name, pass the binding name as `rootBinding`. The pathname supports both HTTP requests and WebSocket connections. -For a custom route prefix, pass the same `prefix` to `buildAgentPath()` and `routeAgentRequest()`. Refer to [Sub-agents](/agents/runtime/execution/sub-agents/#direct-http-and-websocket-urls) for callback and webhook examples. +For a custom route prefix, pass the same `prefix` to `buildAgentPath()` and `routeAgentRequest()`. Refer to [Sub-agents](/agents/agent-class/sub-agents/#direct-http-and-websocket-urls) for callback and webhook examples. ## Instance naming patterns @@ -903,7 +903,7 @@ class SecureAgent extends Agent { @@ -915,7 +915,7 @@ class SecureAgent extends Agent { diff --git a/src/content/docs/agents/runtime/execution/run-workflows.mdx b/src/content/docs/agents/agent-class/run-workflows.mdx similarity index 99% rename from src/content/docs/agents/runtime/execution/run-workflows.mdx rename to src/content/docs/agents/agent-class/run-workflows.mdx index 69589f8ef15..04960a3c463 100644 --- a/src/content/docs/agents/runtime/execution/run-workflows.mdx +++ b/src/content/docs/agents/agent-class/run-workflows.mdx @@ -3,7 +3,7 @@ title: Run Workflows description: Integrate Cloudflare Workflows with Agents for durable, multi-step background processing and failure recovery. pcx_content_type: concept sidebar: - order: 14 + order: 13 products: - agents --- @@ -902,13 +902,13 @@ Workflows cannot open WebSocket connections directly. Use `broadcastToClients()` diff --git a/src/content/docs/agents/runtime/execution/schedule-tasks.mdx b/src/content/docs/agents/agent-class/schedule-tasks.mdx similarity index 98% rename from src/content/docs/agents/runtime/execution/schedule-tasks.mdx rename to src/content/docs/agents/agent-class/schedule-tasks.mdx index 94bb7158614..60d23b0f490 100644 --- a/src/content/docs/agents/runtime/execution/schedule-tasks.mdx +++ b/src/content/docs/agents/agent-class/schedule-tasks.mdx @@ -3,7 +3,7 @@ title: Schedule tasks description: Schedule delayed, date-based, cron, and interval tasks on Agents with persistent SQLite-backed execution. pcx_content_type: reference sidebar: - order: 10 + order: 9 products: - agents --- @@ -704,7 +704,7 @@ Schedule a task for future execution. - `when` - When to execute: `number` (seconds delay), `Date` (specific time), or `string` (cron expression) - `callback` - Name of the method to call - `payload` - Data to pass to the callback (must be JSON-serializable) -- `options.retry` - Optional retry configuration. Refer to [Retries](/agents/runtime/execution/retries/) for details +- `options.retry` - Optional retry configuration. Refer to [Retries](/agents/agent-class/retries/) for details - `options.idempotent` - Deduplicate by callback + payload. Defaults to `true` for cron schedules, `false` for delayed and Date-based schedules **Returns:** A `Schedule` object with the task details @@ -752,7 +752,7 @@ Schedule a task to run repeatedly at a fixed interval. - `intervalSeconds` - Number of seconds between executions (must be greater than 0) - `callback` - Name of the method to call - `payload` - Data to pass to the callback (must be JSON-serializable) -- `options.retry` - Optional retry configuration. Refer to [Retries](/agents/runtime/execution/retries/) for details. +- `options.retry` - Optional retry configuration. Refer to [Retries](/agents/agent-class/retries/) for details. **Returns:** A `Schedule` object with `type: "interval"` @@ -916,24 +916,24 @@ dispose2(); // Now the agent can go idle diff --git a/src/content/docs/agents/runtime/lifecycle/state.mdx b/src/content/docs/agents/agent-class/state.mdx similarity index 95% rename from src/content/docs/agents/runtime/lifecycle/state.mdx rename to src/content/docs/agents/agent-class/state.mdx index 903b195108c..0936d83ba9e 100644 --- a/src/content/docs/agents/runtime/lifecycle/state.mdx +++ b/src/content/docs/agents/agent-class/state.mdx @@ -3,7 +3,7 @@ title: Store and sync state description: Persist and sync Agent state across clients in real time using setState, SQL storage, and bidirectional updates. pcx_content_type: concept sidebar: - order: 7 + order: 2 products: - agents --- @@ -27,7 +27,7 @@ State within an Agent is: Agent state is stored in a SQL database embedded within each individual Agent instance. You can interact with it using the higher-level `this.setState` API (recommended), which allows you to sync state and trigger events on state changes, or by directly querying the database with `this.sql`. :::note[State vs Props] -**State** is persistent data that survives restarts and syncs across clients. **[Props](/agents/runtime/communication/routing/#props)** are one-time initialization arguments passed when an agent is instantiated - use props for configuration that does not need to persist. +**State** is persistent data that survives restarts and syncs across clients. **[Props](/agents/agent-class/routing/#props)** are one-time initialization arguments passed when an agent is instantiated - use props for configuration that does not need to persist. ::: @@ -186,7 +186,7 @@ export class MinimalAgent extends Agent { Use `setState()` to update state. This: 1. Saves to SQLite (persistent) -2. Broadcasts to all connected clients (excluding connections where [`shouldSendProtocolMessages`](/agents/runtime/communication/protocol-messages/) returned `false`) +2. Broadcasts to all connected clients (excluding connections where [`shouldSendProtocolMessages`](/agents/agent-class/protocol-messages/) returned `false`) 3. Triggers `onStateChanged()` (after broadcast; best-effort) @@ -426,7 +426,7 @@ flowchart TD ## State from Workflows -When using [Workflows](/agents/runtime/execution/run-workflows/), you can update agent state from workflow steps: +When using [Workflows](/agents/agent-class/run-workflows/), you can update agent state from workflow steps: @@ -642,7 +642,7 @@ onStateChanged(state: State, source: Connection | "server") { ## Use Agent state as model context -You can combine the state and SQL APIs in your Agent with its ability to [call AI models](/agents/runtime/operations/using-ai-models/) to include historical context within your prompts to a model. Modern Large Language Models (LLMs) often have very large context windows (up to millions of tokens), which allows you to pull relevant context into your prompt directly. +You can combine the state and SQL APIs in your Agent with its ability to [call AI models](/agents/agent-class/using-ai-models/) to include historical context within your prompts to a model. Modern Large Language Models (LLMs) often have very large context windows (up to millions of tokens), which allows you to pull relevant context into your prompt directly. For example, you can use an Agent's built-in SQL database to pull history, query a model with it, and append to that history ahead of the next call to the model: @@ -719,7 +719,7 @@ This works because each instance of an Agent has its own database, and the state @@ -731,12 +731,12 @@ This works because each instance of an Agent has its own database, and the state diff --git a/src/content/docs/agents/runtime/execution/sub-agents.mdx b/src/content/docs/agents/agent-class/sub-agents.mdx similarity index 95% rename from src/content/docs/agents/runtime/execution/sub-agents.mdx rename to src/content/docs/agents/agent-class/sub-agents.mdx index e17ef6cea9e..be931102bc2 100644 --- a/src/content/docs/agents/runtime/execution/sub-agents.mdx +++ b/src/content/docs/agents/agent-class/sub-agents.mdx @@ -5,7 +5,7 @@ description: Spawn child agents with isolated storage and typed RPC using subAge tags: - AI sidebar: - order: 5 + order: 14 products: - agents --- @@ -16,7 +16,7 @@ Spawn child agents as co-located Durable Objects with their own isolated SQLite Use sub-agents when a single user or entity owns an open-ended set of long-lived agents, such as chats, documents, sessions, shards, or projects. Each sub-agent runs in parallel with its own state while the parent coordinates discovery, access control, and lifecycle. -If you want a parent chat agent to dispatch another chat-capable agent during a single turn and render that child's progress inline, use [Agents as tools](/agents/runtime/execution/agent-tools/). Agents as tools are built on sub-agents, but add a parent-side run registry, streaming `agent-tool-event` frames, replay, cancellation, and cleanup. +If you want a parent chat agent to dispatch another chat-capable agent during a single turn and render that child's progress inline, use [Agents as tools](/agents/agent-class/agent-tools/). Agents as tools are built on sub-agents, but add a parent-side run registry, streaming `agent-tool-event` frames, replay, cancellation, and cleanup. ## Quick start @@ -561,7 +561,7 @@ Calling `this.destroy()` inside a sub-agent delegates cleanup to the parent. The ### Workflows from sub-agents -Sub-agents can also start [Workflows](/agents/runtime/execution/run-workflows/) with `this.runWorkflow()`. Workflow tracking is local to the sub-agent's SQLite database, and `AgentWorkflow.agent` routes RPC, callbacks, state updates, and broadcasts back to the originating sub-agent. Parent agents do not automatically list or control child-started workflows. +Sub-agents can also start [Workflows](/agents/agent-class/run-workflows/) with `this.runWorkflow()`. Workflow tracking is local to the sub-agent's SQLite database, and `AgentWorkflow.agent` routes RPC, callbacks, state updates, and broadcasts back to the originating sub-agent. Parent agents do not automatically list or control child-started workflows. Because `SubAgentStub` only exposes user-defined child methods, add child wrapper methods for controls such as `getWorkflow()`, `approveWorkflow()`, or `terminateWorkflow()`, then call those wrappers through `await this.subAgent(Child, name)`. If you pass `runWorkflow(..., { agentBinding })` from a sub-agent, use the root Agent binding name, not a child binding name. @@ -579,6 +579,6 @@ For sub-agent workflow origins, `AgentWorkflow.agent` is RPC-only. Use it to cal - [Think](/agents/harnesses/think/) — `chat()` method for streaming AI turns through sub-agents - [Long-running agents](/agents/concepts/agentic-patterns/long-running-agents/) — sub-agent delegation in the context of multi-week agent lifetimes -- [Callable methods](/agents/runtime/lifecycle/callable-methods/) — RPC via `@callable` and service bindings -- [Agents as tools](/agents/runtime/execution/agent-tools/) — run Think or `AIChatAgent` sub-agents as retained, streaming tools -- [Schedule tasks](/agents/runtime/execution/schedule-tasks/) — scheduling primitives for top-level agents and sub-agents +- [Callable methods](/agents/agent-class/callable-methods/) — RPC via `@callable` and service bindings +- [Agents as tools](/agents/agent-class/agent-tools/) — run Think or `AIChatAgent` sub-agents as retained, streaming tools +- [Schedule tasks](/agents/agent-class/schedule-tasks/) — scheduling primitives for top-level agents and sub-agents diff --git a/src/content/docs/agents/runtime/operations/using-ai-models.mdx b/src/content/docs/agents/agent-class/using-ai-models.mdx similarity index 92% rename from src/content/docs/agents/runtime/operations/using-ai-models.mdx rename to src/content/docs/agents/agent-class/using-ai-models.mdx index 9e5d8ff5066..411b32412c8 100644 --- a/src/content/docs/agents/runtime/operations/using-ai-models.mdx +++ b/src/content/docs/agents/agent-class/using-ai-models.mdx @@ -5,7 +5,7 @@ pcx_content_type: concept tags: - AI sidebar: - order: 19 + order: 16 products: - agents --- @@ -26,13 +26,13 @@ The [AI SDK](https://ai-sdk.dev/docs/introduction) provides a unified interface ## Calling AI Models -You can call models from any method within an Agent, including from HTTP requests using the [`onRequest`](/agents/runtime/agents-api/) handler, when a [scheduled task](/agents/runtime/execution/schedule-tasks/) runs, when handling a WebSocket message in the [`onMessage`](/agents/runtime/communication/websockets/) handler, or from any of your own methods. +You can call models from any method within an Agent, including from HTTP requests using the [`onRequest`](/agents/agent-class/) handler, when a [scheduled task](/agents/agent-class/schedule-tasks/) runs, when handling a WebSocket message in the [`onMessage`](/agents/agent-class/websockets/) handler, or from any of your own methods. Agents can call AI models on their own — autonomously — and can handle long-running responses that take minutes (or longer) to respond in full. If a client disconnects mid-stream, the Agent keeps running and can catch the client up when it reconnects. ### Streaming over WebSockets {/* long-running-model-requests */} -Modern reasoning models can take some time to both generate a response _and_ stream the response back to the client. Instead of buffering the entire response, you can stream it back over [WebSockets](/agents/runtime/communication/websockets/). +Modern reasoning models can take some time to both generate a response _and_ stream the response back to the client. Instead of buffering the entire response, you can stream it back over [WebSockets](/agents/agent-class/websockets/). @@ -79,7 +79,7 @@ export class MyAgent extends Agent { -You can also persist AI model responses back to [Agent state](/agents/runtime/lifecycle/state/) using `this.setState`. If a user disconnects, read the message history back and send it to the user when they reconnect. +You can also persist AI model responses back to [Agent state](/agents/agent-class/state/) using `this.setState`. If a user disconnects, read the message history back and send it to the user when they reconnect. ## Workers AI @@ -242,7 +242,7 @@ export class MyAgent extends Agent { Agents can call models across any service that supports the OpenAI API. For example, you can use the OpenAI SDK to call one of [Google's Gemini models](https://ai.google.dev/gemini-api/docs/openai#node.js) directly from your Agent. -Agents can stream responses back over HTTP using Server-Sent Events (SSE) from within an `onRequest` handler, or by using the native [WebSocket API](/agents/runtime/communication/websockets/) to stream responses back to a client. +Agents can stream responses back over HTTP using Server-Sent Events (SSE) from within an `onRequest` handler, or by using the native [WebSocket API](/agents/agent-class/websockets/) to stream responses back to a client. diff --git a/src/content/docs/agents/runtime/communication/websockets.mdx b/src/content/docs/agents/agent-class/websockets.mdx similarity index 97% rename from src/content/docs/agents/runtime/communication/websockets.mdx rename to src/content/docs/agents/agent-class/websockets.mdx index 077ab9f6687..8a71e008561 100644 --- a/src/content/docs/agents/runtime/communication/websockets.mdx +++ b/src/content/docs/agents/agent-class/websockets.mdx @@ -3,14 +3,14 @@ title: WebSockets description: Handle real-time WebSocket connections, messages, broadcasts, and lifecycle hooks in the Agents SDK. pcx_content_type: reference sidebar: - order: 8 + order: 6 products: - agents --- import { TypeScriptExample, LinkCard } from "~/components"; -Agents support WebSocket connections for real-time, bi-directional communication. This page covers server-side WebSocket handling. For client-side connection, refer to the [Client SDK](/agents/communication-channels/chat/client-sdk/). +Agents support WebSocket connections for real-time, bi-directional communication. This page covers server-side WebSocket handling. For client-side connection, refer to the [Client SDK](/agents/agent-class/communication-channels/chat/client-sdk/). ## Lifecycle hooks @@ -224,7 +224,7 @@ export class ChatAgent extends Agent { | `getConnection` | `(id: string) => Connection \| undefined` | Get connection by ID | | `getConnectionTags` | `(connection, ctx) => string[]` | Override to tag connections | | `broadcast` | `(message, without?: string[]) => void` | Send to all connections | -| `isConnectionReadonly` | `(connection) => boolean` | Check if a connection is [readonly](/agents/runtime/communication/readonly-connections/) | +| `isConnectionReadonly` | `(connection) => boolean` | Check if a connection is [readonly](/agents/agent-class/readonly-connections/) | | `isConnectionProtocolEnabled` | `(connection) => boolean` | Check if protocol messages are enabled for this connection | ## Handling binary data @@ -255,7 +255,7 @@ export class FileAgent extends Agent { :::note -Agents automatically send JSON text frames (identity, state, MCP servers) to every connection. If your client only handles binary data and cannot process these frames, use [`shouldSendProtocolMessages`](/agents/runtime/communication/protocol-messages/) to suppress them. +Agents automatically send JSON text frames (identity, state, MCP servers) to every connection. If your client only handles binary data and cannot process these frames, use [`shouldSendProtocolMessages`](/agents/agent-class/protocol-messages/) to suppress them. ::: ## Error and close handling @@ -554,19 +554,19 @@ For browser connections, use the Agents client SDK: - **Vanilla JS**: `AgentClient` from `agents/client` - **React**: `useAgent` hook from `agents/react` -Refer to [Client SDK](/agents/communication-channels/chat/client-sdk/) for full documentation. +Refer to [Client SDK](/agents/agent-class/communication-channels/chat/client-sdk/) for full documentation. ## Next steps diff --git a/src/content/docs/agents/concepts/agentic-patterns/human-in-the-loop.mdx b/src/content/docs/agents/concepts/agentic-patterns/human-in-the-loop.mdx index bddc68dd5f4..69499d5b5a4 100644 --- a/src/content/docs/agents/concepts/agentic-patterns/human-in-the-loop.mdx +++ b/src/content/docs/agents/concepts/agentic-patterns/human-in-the-loop.mdx @@ -576,7 +576,7 @@ class MultiApprovalAgent extends Agent { diff --git a/src/content/docs/agents/concepts/agentic-patterns/long-running-agents.mdx b/src/content/docs/agents/concepts/agentic-patterns/long-running-agents.mdx index 494be30db9e..32a33b9c9af 100644 --- a/src/content/docs/agents/concepts/agentic-patterns/long-running-agents.mdx +++ b/src/content/docs/agents/concepts/agentic-patterns/long-running-agents.mdx @@ -124,7 +124,7 @@ A hibernated agent can be woken by any of these sources: | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------- | | **HTTP request** | Any request to the agent's URL triggers `onRequest()` | A webhook from GitHub | | **WebSocket connection** | A client connects, triggering `onConnect()` | A team member opens the dashboard | -| **RPC call** | Another Worker or agent calls a method via [service binding](/workers/runtime-apis/bindings/service-bindings/) or [`@callable`](/agents/runtime/lifecycle/callable-methods/) | A coordinator agent delegates a task | +| **RPC call** | Another Worker or agent calls a method via [service binding](/workers/runtime-apis/bindings/service-bindings/) or [`@callable`](/agents/agent-class/callable-methods/) | A coordinator agent delegates a task | | **Scheduled alarm** | A stored schedule fires, triggering your callback | Daily standup reminder at 9am | | **Email** | An inbound email triggers `onEmail()` | A team member replies to a status email | @@ -220,16 +220,16 @@ try { | Seconds | Normal request handling | | Minutes | `keepAlive()` / `keepAliveWhile()` | | Minutes | `startFiber()` when retryable acceptance matters | -| Minutes to hours | [Workflows](/agents/runtime/execution/run-workflows/) | +| Minutes to hours | [Workflows](/agents/agent-class/run-workflows/) | | Hours to days | Async pattern: start job, hibernate, wake on completion | ## Surviving crashes: fibers and recovery An agent can be evicted at any time — a deploy, a platform restart, or hitting resource limits. If the agent was mid-task, that work is lost unless it was checkpointed. -[`runFiber()`](/agents/runtime/execution/durable-execution/) provides crash-recoverable execution. It persists a row in SQLite for the duration of the work, and lets you `stash()` intermediate state. If the agent is evicted, the fiber row survives, and `onFiberRecovered()` is called on the next activation. +[`runFiber()`](/agents/agent-class/durable-execution/) provides crash-recoverable execution. It persists a row in SQLite for the duration of the work, and lets you `stash()` intermediate state. If the agent is evicted, the fiber row survives, and `onFiberRecovered()` is called on the next activation. -Use [`startFiber()`](/agents/runtime/execution/durable-execution/#startfiber) when the important boundary is durable acceptance. It adds an idempotency key, retained status records, inspection, cancellation, and cleanup on top of the same fiber machinery. By default it returns after acceptance; pass `waitForCompletion: true` when the request should stay open until the accepted job reaches a terminal status. This is a good fit for webhooks where the provider may retry delivery and the agent must avoid starting duplicate visible side effects. +Use [`startFiber()`](/agents/agent-class/durable-execution/#startfiber) when the important boundary is durable acceptance. It adds an idempotency key, retained status records, inspection, cancellation, and cleanup on top of the same fiber machinery. By default it returns after acceptance; pass `waitForCompletion: true` when the request should stay open until the accepted job reaches a terminal status. This is a good fit for webhooks where the provider may retry delivery and the agent must avoid starting duplicate visible side effects. ```ts export class ProjectManager extends Agent { @@ -270,7 +270,7 @@ In `wrangler dev`, fiber recovery works identically to production. Kill the wran ::: -For the full API reference — `FiberContext`, `FiberRecoveryContext`, concurrent fibers, inline vs fire-and-forget patterns — refer to [Durable Execution](/agents/runtime/execution/durable-execution/). +For the full API reference — `FiberContext`, `FiberRecoveryContext`, concurrent fibers, inline vs fire-and-forget patterns — refer to [Durable Execution](/agents/agent-class/durable-execution/). ## Handling long async operations @@ -362,7 +362,7 @@ export class ProjectManager extends Agent { ### Pattern: workflow delegation -A production deployment involves multiple steps that must each retry independently — build, test, stage, promote. The project manager should not manage these steps internally; it delegates to a [Workflow](/agents/runtime/execution/run-workflows/) that handles retries and step sequencing: +A production deployment involves multiple steps that must each retry independently — build, test, stage, promote. The project manager should not manage these steps internally; it delegates to a [Workflow](/agents/agent-class/run-workflows/) that handles retries and step sequencing: ```ts export class ProjectManager extends Agent { @@ -535,7 +535,7 @@ Sub-agents have their own state, schedules, durable fibers, and lifecycle. They Because facets do not have independent alarm slots, the top-level parent owns the physical Durable Object alarm. The Agents SDK records which sub-agent owns each schedule or fiber recovery lease, wakes the parent, and routes the callback back into the child. The parent does not need to stay active while the sub-agent works — it can start the work, hibernate, and be woken by the child-owned schedule or recovery check. -For the full `subAgent()` API — typed RPC stubs, client routing, access control, storage isolation, and alarm-backed APIs — refer to [Sub-agents](/agents/runtime/execution/sub-agents/). For AI-specific sub-agent streaming (running full LLM turns through a child agent), refer to [Think: Sub-agent RPC](/agents/harnesses/think/sub-agents/). +For the full `subAgent()` API — typed RPC stubs, client routing, access control, storage isolation, and alarm-backed APIs — refer to [Sub-agents](/agents/agent-class/sub-agents/). For AI-specific sub-agent streaming (running full LLM turns through a child agent), refer to [Think: Sub-agent RPC](/agents/harnesses/think/sub-agents/). ## Recovering interrupted LLM streams The patterns above handle the project manager's coordination work — scheduling, delegating, polling. But the project manager also uses an LLM directly: generating plans, summarizing progress, drafting status emails. Those LLM calls stream tokens over a connection that cannot be resumed if the agent is evicted mid-response. @@ -695,13 +695,13 @@ The agent does not need to run continuously to do any of this. It just needs to ## Related -- [Durable Execution](/agents/runtime/execution/durable-execution/) — `runFiber()`, `startFiber()`, `stash()`, and crash recovery -- [Schedule tasks](/agents/runtime/execution/schedule-tasks/) — delayed, cron, and interval tasks -- [Retries](/agents/runtime/execution/retries/) — retry options and patterns -- [Workflows](/agents/runtime/execution/run-workflows/) — durable multi-step processing -- [Store and sync state](/agents/runtime/lifecycle/state/) — `setState()` and persistence -- [WebSockets](/agents/runtime/communication/websockets/) — lifecycle hooks and hibernation -- [Callable methods](/agents/runtime/lifecycle/callable-methods/) — RPC via `@callable` and service bindings -- [Email routing](/agents/communication-channels/email/) — receiving inbound email -- [Webhooks](/agents/communication-channels/webhooks/) — receiving external events +- [Durable Execution](/agents/agent-class/durable-execution/) — `runFiber()`, `startFiber()`, `stash()`, and crash recovery +- [Schedule tasks](/agents/agent-class/schedule-tasks/) — delayed, cron, and interval tasks +- [Retries](/agents/agent-class/retries/) — retry options and patterns +- [Workflows](/agents/agent-class/run-workflows/) — durable multi-step processing +- [Store and sync state](/agents/agent-class/state/) — `setState()` and persistence +- [WebSockets](/agents/agent-class/websockets/) — lifecycle hooks and hibernation +- [Callable methods](/agents/agent-class/callable-methods/) — RPC via `@callable` and service bindings +- [Email routing](/agents/agent-class/communication-channels/email/) — receiving inbound email +- [Webhooks](/agents/agent-class/communication-channels/webhooks/) — receiving external events - [Human in the loop](/agents/concepts/agentic-patterns/human-in-the-loop/) — approval flows diff --git a/src/content/docs/agents/concepts/calling-llms.mdx b/src/content/docs/agents/concepts/calling-llms.mdx index 45d48faf6ac..c44b532585b 100644 --- a/src/content/docs/agents/concepts/calling-llms.mdx +++ b/src/content/docs/agents/concepts/calling-llms.mdx @@ -14,11 +14,11 @@ import { TypeScriptExample, LinkCard } from "~/components"; Agents change how you work with LLMs. In a stateless Worker, every request starts from scratch — you reconstruct context, call a model, return the response, and forget everything. An Agent keeps state between calls, stays connected to clients over WebSocket, and can call models on its own schedule without a user present. -This page covers the patterns that become possible when your LLM calls happen inside a stateful Agent. For provider setup and code examples, refer to [Using AI Models](/agents/runtime/operations/using-ai-models/). +This page covers the patterns that become possible when your LLM calls happen inside a stateful Agent. For provider setup and code examples, refer to [Using AI Models](/agents/agent-class/using-ai-models/). ## State as context -Every Agent has a built-in [SQL database](/agents/runtime/lifecycle/state/) and key-value state. Instead of passing an entire conversation history from the client on every request, the Agent stores it and builds prompts from its own storage. +Every Agent has a built-in [SQL database](/agents/agent-class/state/) and key-value state. Instead of passing an entire conversation history from the client on every request, the Agent stores it and builds prompts from its own storage. @@ -82,7 +82,7 @@ export class MyAgent extends Agent { -With [`AIChatAgent`](/agents/communication-channels/chat/chat-agents/), this is handled automatically — messages are persisted to SQLite and streams resume on reconnect. +With [`AIChatAgent`](/agents/agent-class/communication-channels/chat/chat-agents/), this is handled automatically — messages are persisted to SQLite and streams resume on reconnect. ## Autonomous model calls @@ -202,24 +202,24 @@ For provider-level caching and rate limit management across multiple agents, use diff --git a/src/content/docs/agents/concepts/conversation-state-and-memory.mdx b/src/content/docs/agents/concepts/conversation-state-and-memory.mdx index ecd002751b1..7c4c0220643 100644 --- a/src/content/docs/agents/concepts/conversation-state-and-memory.mdx +++ b/src/content/docs/agents/concepts/conversation-state-and-memory.mdx @@ -16,7 +16,7 @@ Agents need memory to be useful over time. Without it, every conversation starts The [Session API](/agents/runtime/lifecycle/sessions/) provides the memory layer for agents built on the Cloudflare Agents SDK. It manages two kinds of memory: **conversation history** (the messages and tool calls that make up a session) and **context memory** (persistent blocks injected into the system prompt that the agent can read, write, search, and load). -Use this page when you need more than simple synced state or flat chat history. For small UI state, use [Store and sync state](/agents/runtime/lifecycle/state/). For basic chat persistence, `AIChatAgent` stores messages for you. For opinionated long-term memory, Think builds on Session and context blocks. +Use this page when you need more than simple synced state or flat chat history. For small UI state, use [Store and sync state](/agents/agent-class/state/). For basic chat persistence, `AIChatAgent` stores messages for you. For opinionated long-term memory, Think builds on Session and context blocks. :::caution[Experimental] @@ -562,7 +562,7 @@ const truncated = truncateOlderMessages(history); diff --git a/src/content/docs/agents/concepts/tools.mdx b/src/content/docs/agents/concepts/tools.mdx index 560a09fbd7f..f3e4adc0309 100644 --- a/src/content/docs/agents/concepts/tools.mdx +++ b/src/content/docs/agents/concepts/tools.mdx @@ -51,9 +51,9 @@ Execution location describes where a tool implementation runs. It is independent | Location | Use when | Start here | | ------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | -| Worker | The tool calls an API, queries SQL, or uses server-side bindings and secrets. | [Server-side tools](/agents/communication-channels/chat/chat-agents/#server-side-tools) | -| Browser | The tool needs geolocation, clipboard, local storage, or other browser APIs. | [Client-side tools](/agents/communication-channels/chat/chat-agents/#client-side-tools) | -| Another Agent | A chat-capable Agent should execute as a retained, streaming tool. | [Agents as tools](/agents/runtime/execution/agent-tools/) | +| Worker | The tool calls an API, queries SQL, or uses server-side bindings and secrets. | [Server-side tools](/agents/agent-class/communication-channels/chat/chat-agents/#server-side-tools) | +| Browser | The tool needs geolocation, clipboard, local storage, or other browser APIs. | [Client-side tools](/agents/agent-class/communication-channels/chat/chat-agents/#client-side-tools) | +| Another Agent | A chat-capable Agent should execute as a retained, streaming tool. | [Agents as tools](/agents/agent-class/agent-tools/) | With direct tool calls, the model calls the tool and the framework routes execution to the configured location. diff --git a/src/content/docs/agents/concepts/what-are-agents.mdx b/src/content/docs/agents/concepts/what-are-agents.mdx index 0e6ab0ca45f..2784b2271c9 100644 --- a/src/content/docs/agents/concepts/what-are-agents.mdx +++ b/src/content/docs/agents/concepts/what-are-agents.mdx @@ -97,12 +97,12 @@ The Cloudflare Agents SDK provides the infrastructure for building production ag diff --git a/src/content/docs/agents/concepts/workflows.mdx b/src/content/docs/agents/concepts/workflows.mdx index df4a47df04f..2a15c661ae2 100644 --- a/src/content/docs/agents/concepts/workflows.mdx +++ b/src/content/docs/agents/concepts/workflows.mdx @@ -187,7 +187,7 @@ A Workflow updates Agent state at key milestones using `step.updateAgentState()` diff --git a/src/content/docs/agents/examples/browser-agent.mdx b/src/content/docs/agents/examples/browser-agent.mdx index bfd90b3d310..aa453eb9e1c 100644 --- a/src/content/docs/agents/examples/browser-agent.mdx +++ b/src/content/docs/agents/examples/browser-agent.mdx @@ -144,7 +144,7 @@ async () => { ## Use with an Agent -The typical pattern is to create browser tools inside an [`AIChatAgent`](/agents/communication-channels/chat/chat-agents/) message handler, which gives you message persistence and streaming: +The typical pattern is to create browser tools inside an [`AIChatAgent`](/agents/agent-class/communication-channels/chat/chat-agents/) message handler, which gives you message persistence and streaming: diff --git a/src/content/docs/agents/examples/chat-agent.mdx b/src/content/docs/agents/examples/chat-agent.mdx index 32169801577..2bfe7039a1f 100644 --- a/src/content/docs/agents/examples/chat-agent.mdx +++ b/src/content/docs/agents/examples/chat-agent.mdx @@ -349,19 +349,19 @@ Your chat agent has: diff --git a/src/content/docs/agents/examples/email-agent.mdx b/src/content/docs/agents/examples/email-agent.mdx index b63d259cd59..c061438fa8f 100644 --- a/src/content/docs/agents/examples/email-agent.mdx +++ b/src/content/docs/agents/examples/email-agent.mdx @@ -748,18 +748,18 @@ Useful when sending emails through external services while maintaining secure re diff --git a/src/content/docs/agents/examples/slack-agent.mdx b/src/content/docs/agents/examples/slack-agent.mdx index 87bcfc9d366..9c90be9c365 100644 --- a/src/content/docs/agents/examples/slack-agent.mdx +++ b/src/content/docs/agents/examples/slack-agent.mdx @@ -461,7 +461,7 @@ export class MyAgent extends SlackAgent { - Add rate limiting to prevent abuse - Implement conversation state management - Use [Workers Analytics Engine](/analytics/analytics-engine/) to track usage -- Add [schedules](/agents/runtime/execution/schedule-tasks/) for scheduled tasks +- Add [schedules](/agents/agent-class/schedule-tasks/) for scheduled tasks ## Related resources diff --git a/src/content/docs/agents/examples/voice-agent.mdx b/src/content/docs/agents/examples/voice-agent.mdx index 1a439fa4cd6..7be70b67871 100644 --- a/src/content/docs/agents/examples/voice-agent.mdx +++ b/src/content/docs/agents/examples/voice-agent.mdx @@ -291,18 +291,18 @@ export class MyVoiceAgent extends VoiceAgent { diff --git a/src/content/docs/agents/harnesses/index.mdx b/src/content/docs/agents/harnesses/index.mdx index ee5c55836dc..a711b50bcc5 100644 --- a/src/content/docs/agents/harnesses/index.mdx +++ b/src/content/docs/agents/harnesses/index.mdx @@ -12,13 +12,13 @@ A harness is the loop that makes an agent behave like an agent instead of a sing It is responsible for the turn-by-turn work around the model: building the prompt, loading memory, selecting tools, handling tool results, streaming responses, persisting messages, and deciding whether the agent should continue or stop. -To build a new agent, start with the [Pi harness](/agents/harnesses/pi/), which runs Pi Durable in a Durable Object. You can also run [OpenCode](/agents/harnesses/opencode/), or build the loop yourself on top of the [Agents SDK runtime](/agents/runtime/agents-api/). +To build a new agent, start with the [Pi harness](/agents/harnesses/pi/), which runs Pi Durable in a Durable Object. You can also run [OpenCode](/agents/harnesses/opencode/), or build the loop yourself on top of the [Agents SDK runtime](/agents/agent-class/). ## How harnesses fit Harnesses sit on top of the Agents SDK runtime: -- **The runtime** gives the agent durable infrastructure: the [`Agent` class](/agents/runtime/lifecycle/agent-class/), [state](/agents/runtime/lifecycle/state/), [sessions](/agents/runtime/lifecycle/sessions/), [routing](/agents/runtime/communication/routing/), [WebSockets](/agents/runtime/communication/websockets/), [scheduling](/agents/runtime/execution/schedule-tasks/), [fibers](/agents/runtime/execution/durable-execution/), and [observability](/agents/runtime/operations/observability/). +- **The runtime** gives the agent durable infrastructure: the [`Agent` class](/agents/agent-class/internals/), [state](/agents/agent-class/state/), [sessions](/agents/runtime/lifecycle/sessions/), [routing](/agents/agent-class/routing/), [WebSockets](/agents/agent-class/websockets/), [scheduling](/agents/agent-class/schedule-tasks/), [fibers](/agents/agent-class/durable-execution/), and [observability](/agents/runtime/operations/observability/). - **The harness** gives the agent behavior: model calls, prompt construction, tool selection, stream handling, memory strategy, and lifecycle hooks. The runtime answers “where does this agent live and how does it stay durable?” The harness answers “what does this agent do on each turn?” @@ -60,7 +60,7 @@ A harness usually owns: @@ -72,6 +72,6 @@ A harness usually owns: diff --git a/src/content/docs/agents/harnesses/think/channels.mdx b/src/content/docs/agents/harnesses/think/channels.mdx index 5667b653a6f..b6edc9e928f 100644 --- a/src/content/docs/agents/harnesses/think/channels.mdx +++ b/src/content/docs/agents/harnesses/think/channels.mdx @@ -183,4 +183,4 @@ const unsubscribe = subscribe("channel", (event) => { - [Messengers](/agents/harnesses/think/messengers/) — Chat SDK webhook setup and delivery in depth. - [Actions](/agents/harnesses/think/actions/) — record reply attachments for `renderAttachment()`. -- [Voice](/agents/communication-channels/voice/) — real-time speech surfaces. +- [Voice](/agents/agent-class/communication-channels/voice/) — real-time speech surfaces. diff --git a/src/content/docs/agents/harnesses/think/client-tools.mdx b/src/content/docs/agents/harnesses/think/client-tools.mdx index 4c3433368a1..eb76006bb6f 100644 --- a/src/content/docs/agents/harnesses/think/client-tools.mdx +++ b/src/content/docs/agents/harnesses/think/client-tools.mdx @@ -118,7 +118,7 @@ When a turn produces several client tool calls at once, Think waits for **all** A Durable Object can be evicted at any time, including while a turn is paused on an approval prompt or a client-side tool call. [`Think` durable recovery](/agents/harnesses/think/recovery/) is always on. The SDK treats such a turn as waiting on the human, not stuck. It parks the turn instead of failing it. The user's eventual approval or tool result resumes the conversation. -For which interactions are exempt from recovery budgets, refer to [Turns waiting on a human are not sealed](/agents/communication-channels/chat/chat-agents/#turns-waiting-on-a-human-are-not-sealed). +For which interactions are exempt from recovery budgets, refer to [Turns waiting on a human are not sealed](/agents/agent-class/communication-channels/chat/chat-agents/#turns-waiting-on-a-human-are-not-sealed). ## Message concurrency diff --git a/src/content/docs/agents/harnesses/think/index.mdx b/src/content/docs/agents/harnesses/think/index.mdx index 84cdc0b0a9b..d8edd145de3 100644 --- a/src/content/docs/agents/harnesses/think/index.mdx +++ b/src/content/docs/agents/harnesses/think/index.mdx @@ -151,7 +151,7 @@ For span attributes, payload controls, exporting traces, and direct AI SDK v6 or ## Think vs AIChatAgent -Both Think and [`AIChatAgent`](/agents/communication-channels/chat/chat-agents/) extend `Agent` and speak the same `cf_agent_chat_*` WebSocket protocol. They serve different goals. +Both Think and [`AIChatAgent`](/agents/agent-class/communication-channels/chat/chat-agents/) extend `Agent` and speak the same `cf_agent_chat_*` WebSocket protocol. They serve different goals. **AIChatAgent** is a protocol adapter. You override `onChatMessage` and are responsible for calling `streamText`, wiring tools, converting messages, and returning a `Response`. AIChatAgent handles the plumbing — message persistence, streaming, abort, resume — but the LLM call is entirely your concern. @@ -300,9 +300,9 @@ export class Assistant extends Think { The supported pattern is "add context, then run a turn": call `addMessages()`, then `runTurn()`. -Use `chat()` for low-level parent-to-child streaming when your code owns forwarding, cancellation, and replay policy. Use [Agents as tools](/agents/runtime/execution/agent-tools/) when a parent model or workflow delegates to a child agent and you want retained child runs, event replay, abort bridging, and UI drill-in. +Use `chat()` for low-level parent-to-child streaming when your code owns forwarding, cancellation, and replay policy. Use [Agents as tools](/agents/agent-class/agent-tools/) when a parent model or workflow delegates to a child agent and you want retained child runs, event replay, abort bridging, and UI drill-in. -Use [`startFiber()`](/agents/runtime/execution/durable-execution/#startfiber) outside Think when the durable unit is an application job around a turn: accepting a webhook once, restoring a serialized channel or thread target, posting a visible reply, or recording app-level recovery policy. Think submissions own conversation admission and turn serialization; managed fibers own external job acceptance, idempotent side effects, and application recovery. +Use [`startFiber()`](/agents/agent-class/durable-execution/#startfiber) outside Think when the durable unit is an application job around a turn: accepting a webhook once, restoring a serialized channel or thread target, posting a visible reply, or recording app-level recovery policy. Think submissions own conversation admission and turn serialization; managed fibers own external job acceptance, idempotent side effects, and application recovery. ## In this section @@ -394,8 +394,8 @@ Think's design is inspired by [Pi](https://pi.dev). ## Related - [Sessions](/agents/runtime/lifecycle/sessions/) — context blocks, compaction, search, multi-session (the storage layer Think builds on) -- [Sub-agents](/agents/runtime/execution/sub-agents/) — `subAgent()`, `abortSubAgent()`, `deleteSubAgent()` (the base Agent methods for spawning children) -- [Chat agents](/agents/communication-channels/chat/chat-agents/) — `AIChatAgent` for when you need full control over the LLM call +- [Sub-agents](/agents/agent-class/sub-agents/) — `subAgent()`, `abortSubAgent()`, `deleteSubAgent()` (the base Agent methods for spawning children) +- [Chat agents](/agents/agent-class/communication-channels/chat/chat-agents/) — `AIChatAgent` for when you need full control over the LLM call - [Long-running agents](/agents/concepts/agentic-patterns/long-running-agents/) — sub-agent delegation patterns for multi-week agent lifetimes -- [Durable execution](/agents/runtime/execution/durable-execution/) — `runFiber()` and crash recovery (used by `chatRecovery`) +- [Durable execution](/agents/agent-class/durable-execution/) — `runFiber()` and crash recovery (used by `chatRecovery`) - [Browse the web](/agents/tools/browser/) — full CDP helper API reference diff --git a/src/content/docs/agents/harnesses/think/messengers.mdx b/src/content/docs/agents/harnesses/think/messengers.mdx index ca99f07f7f6..882b3488411 100644 --- a/src/content/docs/agents/harnesses/think/messengers.mdx +++ b/src/content/docs/agents/harnesses/think/messengers.mdx @@ -196,4 +196,4 @@ Every custom messenger must provide `verifyWebhook` or explicitly use `verifyWeb The `examples/think-chat-sdk` example demonstrates the Think-native `getMessengers()` path with a small Vite dashboard that inspects the root Think conversation over the Agent WebSocket. -The `examples/chat-sdk-messenger` example demonstrates a larger manual ingress agent with an admin dashboard, menu handling, and application-owned reply fibers. Use `getMessengers()` for the simple Think-native path. Use the example when you need to own the Chat SDK runtime and control-plane UI yourself. Refer to [Chat SDK state](/agents/runtime/communication/chat-sdk/) for the underlying state adapter. +The `examples/chat-sdk-messenger` example demonstrates a larger manual ingress agent with an admin dashboard, menu handling, and application-owned reply fibers. Use `getMessengers()` for the simple Think-native path. Use the example when you need to own the Chat SDK runtime and control-plane UI yourself. Refer to [Chat SDK state](/agents/agent-class/communication-channels/chat-sdk/) for the underlying state adapter. diff --git a/src/content/docs/agents/harnesses/think/recovery.mdx b/src/content/docs/agents/harnesses/think/recovery.mdx index 79caf2d7a74..a4f034e5477 100644 --- a/src/content/docs/agents/harnesses/think/recovery.mdx +++ b/src/content/docs/agents/harnesses/think/recovery.mdx @@ -10,7 +10,7 @@ products: import { TypeScriptExample } from "~/components"; -Think always wraps chat turns in recoverable [fibers](/agents/runtime/execution/durable-execution/). If the Durable Object is evicted mid-stream, Think reconstructs any buffered chunks. It persists partial output and schedules a continuation or retry. +Think always wraps chat turns in recoverable [fibers](/agents/agent-class/durable-execution/). If the Durable Object is evicted mid-stream, Think reconstructs any buffered chunks. It persists partial output and schedules a continuation or retry. :::note Durable recovery works without configuration. Most apps never need to configure it. Use this page for provider-specific recovery, a stall watchdog, or terminal behavior. @@ -116,7 +116,7 @@ onChatRecovery(ctx: ChatRecoveryContext): ChatRecoveryOptions { Use `ctx.createdAt` to skip stale recoveries. For example, if the interrupted turn is older than a few minutes, return `{ continue: false }` so the partial response is preserved without starting an old continuation. -Durable bookkeeping remains active when automatic continuation is not appropriate. Return `{ continue: false }` to prevent another model call. For cancellation, side-effect, and cost controls, refer to [Control automatic continuation](/agents/communication-channels/chat/chat-agents/#control-automatic-continuation). +Durable bookkeeping remains active when automatic continuation is not appropriate. Return `{ continue: false }` to prevent another model call. For cancellation, side-effect, and cost controls, refer to [Control automatic continuation](/agents/agent-class/communication-channels/chat/chat-agents/#control-automatic-continuation). ### Recovery budgets and limits @@ -159,7 +159,7 @@ export class MyAgent extends Think { | `terminalMessage` | generic message | Message shown to the user when recovery is given up on. | | `onExhausted` | — | Called once when recovery is given up on. Inspect `ctx.reason`. | -`ctx.reason` on the exhausted hook is one of: `no_progress_timeout` (stuck), `max_attempts_exceeded` (no-progress alarm loop), `work_budget_exceeded` (runaway), `interrupted_attempts_exceeded` (the turn's attempts kept dying, for example in memory-limit resets), `out_of_memory` (sealed by the alarm memory-limit circuit breaker), `recovery_aborted` (your `shouldKeepRecovering` returned `false`), or `stable_timeout` (extreme churn). Refer to [Stream recovery](/agents/communication-channels/chat/chat-agents/#stream-recovery) for the full shared reference. Think and `@cloudflare/ai-chat` use the same recovery configuration. +`ctx.reason` on the exhausted hook is one of: `no_progress_timeout` (stuck), `max_attempts_exceeded` (no-progress alarm loop), `work_budget_exceeded` (runaway), `interrupted_attempts_exceeded` (the turn's attempts kept dying, for example in memory-limit resets), `out_of_memory` (sealed by the alarm memory-limit circuit breaker), `recovery_aborted` (your `shouldKeepRecovering` returned `false`), or `stable_timeout` (extreme churn). Refer to [Stream recovery](/agents/agent-class/communication-channels/chat/chat-agents/#stream-recovery) for the full shared reference. Think and `@cloudflare/ai-chat` use the same recovery configuration. ## Repairing interrupted tool calls diff --git a/src/content/docs/agents/harnesses/think/tools.mdx b/src/content/docs/agents/harnesses/think/tools.mdx index 51bee943ad4..5328b49d3f6 100644 --- a/src/content/docs/agents/harnesses/think/tools.mdx +++ b/src/content/docs/agents/harnesses/think/tools.mdx @@ -24,7 +24,7 @@ On every turn, Think merges tools from multiple sources. Later sources override 6. **MCP tools** — from connected MCP servers when `includeMcpTools` is `true` 7. **Client tools** — from the browser (refer to [Client tools](/agents/harnesses/think/client-tools/)) -Tools belong to the agent running the turn. For parent-child orchestration, use [Agents as tools](/agents/runtime/execution/agent-tools/) instead of passing one-off tools through `chat()`. +Tools belong to the agent running the turn. For parent-child orchestration, use [Agents as tools](/agents/agent-class/agent-tools/) instead of passing one-off tools through `chat()`. ## Built-in workspace tools diff --git a/src/content/docs/agents/harnesses/think/workflows.mdx b/src/content/docs/agents/harnesses/think/workflows.mdx index c4d73e6ef7e..c410572beb4 100644 --- a/src/content/docs/agents/harnesses/think/workflows.mdx +++ b/src/content/docs/agents/harnesses/think/workflows.mdx @@ -175,6 +175,6 @@ getScheduledTasks() { Use [`submitMessages()`](/agents/harnesses/think/programmatic-submissions/) for durable one-off turns where the caller can inspect submission status later. -Use [`startFiber()`](/agents/runtime/execution/durable-execution/#startfiber) for app-owned idempotent Agent jobs that need recovery inside the Agent. Think's workflow notification delivery does not use fibers; it uses a private outbox because it needs to store an event until delivery succeeds. +Use [`startFiber()`](/agents/agent-class/durable-execution/#startfiber) for app-owned idempotent Agent jobs that need recovery inside the Agent. Think's workflow notification delivery does not use fibers; it uses a private outbox because it needs to store an event until delivery succeeds. Use Workflows when the process has multiple deterministic steps, long waits, or human approval. diff --git a/src/content/docs/agents/index.mdx b/src/content/docs/agents/index.mdx index 98d691c38c2..5c127c3d098 100644 --- a/src/content/docs/agents/index.mdx +++ b/src/content/docs/agents/index.mdx @@ -24,9 +24,9 @@ Deploy once and Cloudflare runs your agents across its global network, scaling t Agents on Cloudflare are composed from four parts: -- **Communication channels** define how users and systems reach your agent, such as [chat](/agents/communication-channels/chat/), [voice](/agents/communication-channels/voice/), [email](/agents/communication-channels/email/), [Slack](/agents/communication-channels/slack/), [webhooks](/agents/communication-channels/webhooks/), and other event sources. -- **The agent harness** defines the loop: how the agent calls models, selects tools, handles tool results, streams responses, and decides whether to continue. Use the [Pi harness](/agents/harnesses/pi/) to run Pi Durable in a Durable Object, or build your own loop directly on the [Agents SDK runtime](/agents/runtime/agents-api/). -- **The Agents SDK runtime** provides durable infrastructure: the [`Agent` class](/agents/runtime/lifecycle/agent-class/), [state](/agents/runtime/lifecycle/state/), [sessions](/agents/runtime/lifecycle/sessions/), [routing](/agents/runtime/communication/routing/), [WebSockets](/agents/runtime/communication/websockets/), [scheduling](/agents/runtime/execution/schedule-tasks/), [fibers](/agents/runtime/execution/durable-execution/), and [observability](/agents/runtime/operations/observability/). +- **Communication channels** define how users and systems reach your agent, such as [chat](/agents/agent-class/communication-channels/chat/), [voice](/agents/agent-class/communication-channels/voice/), [email](/agents/agent-class/communication-channels/email/), [Slack](/agents/agent-class/communication-channels/slack/), [webhooks](/agents/agent-class/communication-channels/webhooks/), and other event sources. +- **The agent harness** defines the loop: how the agent calls models, selects tools, handles tool results, streams responses, and decides whether to continue. Use the [Pi harness](/agents/harnesses/pi/) to run Pi Durable in a Durable Object, or build your own loop directly on the [Agents SDK runtime](/agents/agent-class/). +- **The Agents SDK runtime** provides durable infrastructure: the [`Agent` class](/agents/agent-class/internals/), [state](/agents/agent-class/state/), [sessions](/agents/runtime/lifecycle/sessions/), [routing](/agents/agent-class/routing/), [WebSockets](/agents/agent-class/websockets/), [scheduling](/agents/agent-class/schedule-tasks/), [fibers](/agents/agent-class/durable-execution/), and [observability](/agents/runtime/operations/observability/). - **Tools** give the agent capabilities: [browser automation](/agents/tools/browser/), [sandboxed code execution](/agents/tools/sandbox/), [AI Search](/agents/tools/ai-search/), [MCP tools](/agents/tools/mcp/), and [payments](/agents/tools/payments/). [Code Mode](/agents/tools/codemode/) lets models discover and orchestrate multiple tools by writing code. ### Get started diff --git a/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx b/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx index 7026eae9aaa..4600cac1fbf 100644 --- a/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx @@ -50,7 +50,7 @@ export class MyMCP extends McpAgent { -This means that each instance of your MCP server has its own durable state, backed by a [Durable Object](/durable-objects/), with its own [SQL database](/agents/runtime/lifecycle/state/). +This means that each instance of your MCP server has its own durable state, backed by a [Durable Object](/durable-objects/), with its own [SQL database](/agents/agent-class/state/). A stateless server can define [tools](/agents/model-context-protocol/protocol/tools/) with `@modelcontextprotocol/server` and serve them through `createMcpHandler`. @@ -335,13 +335,13 @@ For a stateless server, read the same props with `getMcpAuthContext()`. Refer to ## State synchronization APIs -The `McpAgent` class provides full access to the [Agent state APIs](/agents/runtime/lifecycle/state/): +The `McpAgent` class provides full access to the [Agent state APIs](/agents/agent-class/state/): -- [`state`](/agents/runtime/lifecycle/state/) — Current persisted state -- [`initialState`](/agents/runtime/lifecycle/state/#set-the-initial-state-for-an-agent) — Default state when instance starts -- [`setState`](/agents/runtime/lifecycle/state/) — Update and persist state -- [`onStateChanged`](/agents/runtime/lifecycle/state/#synchronizing-state) — React to state changes -- [`sql`](/agents/runtime/agents-api/#sql-api) — Execute SQL queries on embedded database +- [`state`](/agents/agent-class/state/) — Current persisted state +- [`initialState`](/agents/agent-class/state/#set-the-initial-state-for-an-agent) — Default state when instance starts +- [`setState`](/agents/agent-class/state/) — Update and persist state +- [`onStateChanged`](/agents/agent-class/state/#synchronizing-state) — React to state changes +- [`sql`](/agents/agent-class/#sql-api) — Execute SQL queries on embedded database :::note[State resets after the session ends] Each client session is backed by an instance of the `McpAgent` class. This means that when the same client reconnects, they will start a new session, and the state will be reset. diff --git a/src/content/docs/agents/model-context-protocol/apis/client-api.mdx b/src/content/docs/agents/model-context-protocol/apis/client-api.mdx index c2c56eb2b9d..c58ec07c807 100644 --- a/src/content/docs/agents/model-context-protocol/apis/client-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/client-api.mdx @@ -62,7 +62,7 @@ export class MyAgent extends Agent { -Connections persist in the agent's [SQL storage](/agents/runtime/lifecycle/state/), and when an agent connects to an MCP server, all tools from that server become available automatically. +Connections persist in the agent's [SQL storage](/agents/agent-class/state/), and when an agent connects to an MCP server, all tools from that server become available automatically. ## Adding MCP servers @@ -751,7 +751,7 @@ async addMcpServer( - `transport` — Transport layer configuration: - `headers` — Custom HTTP headers for authentication - `type` — Transport type: `"auto"` (default), `"streamable-http"`, or `"sse"` - - `retry` — Retry options for connection and reconnection attempts. Persisted and used when restoring connections after hibernation or after OAuth completion. Default: 3 attempts, 500ms base delay, 5s max delay. Refer to [Retries](/agents/runtime/execution/retries/) for details on `RetryOptions`. + - `retry` — Retry options for connection and reconnection attempts. Persisted and used when restoring connections after hibernation or after OAuth completion. Default: 3 attempts, 500ms base delay, 5s max delay. Refer to [Retries](/agents/agent-class/retries/) for details on `RetryOptions`. #### Parameters (RPC transport) @@ -1091,7 +1091,7 @@ const disposable = this.mcp.onServerStateChanged(() => { :::note -MCP server list broadcasts (`cf_agent_mcp_servers`) are automatically filtered to exclude connections where [`shouldSendProtocolMessages`](/agents/runtime/communication/protocol-messages/) returned `false`. +MCP server list broadcasts (`cf_agent_mcp_servers`) are automatically filtered to exclude connections where [`shouldSendProtocolMessages`](/agents/agent-class/protocol-messages/) returned `false`. ::: ### Lifecycle methods @@ -1156,7 +1156,7 @@ await this.mcp.waitForConnections({ timeout: 10_000 }); ``` :::note -`AIChatAgent` calls this automatically via its [`waitForMcpConnections`](/agents/communication-channels/chat/chat-agents/#waitformcpconnections) property (defaults to `{ timeout: 10_000 }`). You only need `waitForConnections()` directly when using `Agent` with MCP, or when you want finer control inside `onChatMessage`. +`AIChatAgent` calls this automatically via its [`waitForMcpConnections`](/agents/agent-class/communication-channels/chat/chat-agents/#waitformcpconnections) property (defaults to `{ timeout: 10_000 }`). You only need `waitForConnections()` directly when using `Agent` with MCP, or when you want finer control inside `onChatMessage`. ::: #### `this.mcp.closeConnection()` @@ -1268,12 +1268,12 @@ export class MyAgent extends Agent { diff --git a/src/content/docs/agents/model-context-protocol/guides/connect-mcp-client.mdx b/src/content/docs/agents/model-context-protocol/guides/connect-mcp-client.mdx index d01eff88b8b..d9cde1c94ee 100644 --- a/src/content/docs/agents/model-context-protocol/guides/connect-mcp-client.mdx +++ b/src/content/docs/agents/model-context-protocol/guides/connect-mcp-client.mdx @@ -230,7 +230,7 @@ You created an Agent that can: - List all available tools from connected servers - Monitor connection status -Connections persist in the Agent's [SQL storage](/agents/runtime/lifecycle/state/), so they remain active across requests. +Connections persist in the Agent's [SQL storage](/agents/agent-class/state/), so they remain active across requests. ## Next steps diff --git a/src/content/docs/agents/platform/limits.mdx b/src/content/docs/agents/platform/limits.mdx index b531c72fad9..7073926e31f 100644 --- a/src/content/docs/agents/platform/limits.mdx +++ b/src/content/docs/agents/platform/limits.mdx @@ -28,6 +28,6 @@ Many limits are inherited from those applied to Workers scripts and/or Durable O [^2]: You can deploy up to [500 scripts per account](/workers/platform/limits/), but each script (project) can define multiple Agents. Each deployed script can be up to 10 MB on the [Workers Paid Plan](/workers/platform/pricing/#workers) -[^3]: Compute (CPU) time per Agent is limited to 30 seconds, but this is refreshed when an Agent receives a new HTTP request, runs a [scheduled task](/agents/runtime/execution/schedule-tasks/), or an incoming WebSocket message. +[^3]: Compute (CPU) time per Agent is limited to 30 seconds, but this is refreshed when an Agent receives a new HTTP request, runs a [scheduled task](/agents/agent-class/schedule-tasks/), or an incoming WebSocket message. diff --git a/src/content/docs/agents/runtime/communication/index.mdx b/src/content/docs/agents/runtime/communication/index.mdx deleted file mode 100644 index afc1b725017..00000000000 --- a/src/content/docs/agents/runtime/communication/index.mdx +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: Communication -pcx_content_type: navigation -sidebar: - order: 2 - group: - hideIndex: true ---- - -import { DirectoryListing } from "~/components"; - - diff --git a/src/content/docs/agents/runtime/execution/agent-skills.mdx b/src/content/docs/agents/runtime/execution/agent-skills.mdx index 580acff3445..7bef060ae58 100644 --- a/src/content/docs/agents/runtime/execution/agent-skills.mdx +++ b/src/content/docs/agents/runtime/execution/agent-skills.mdx @@ -16,7 +16,7 @@ Agent Skills are on-demand instructions, resources, and scripts. A skill source Agent Skills are experimental, and script execution in particular is early. The API may change in a future release. ::: -The skills engine lives in `agents/skills` and is framework-agnostic, so any agent (including a plain [`AIChatAgent`](/agents/communication-channels/chat/chat-agents/) `onChatMessage`) can build a `SkillRegistry`. [`@cloudflare/think`](/agents/harnesses/think/) re-exports it as the `skills` namespace and wires `getSkills()` into the turn automatically. +The skills engine lives in `agents/skills` and is framework-agnostic, so any agent (including a plain [`AIChatAgent`](/agents/agent-class/communication-channels/chat/chat-agents/) `onChatMessage`) can build a `SkillRegistry`. [`@cloudflare/think`](/agents/harnesses/think/) re-exports it as the `skills` namespace and wires `getSkills()` into the turn automatically. ## Using skills with Think diff --git a/src/content/docs/agents/runtime/lifecycle/get-current-agent.mdx b/src/content/docs/agents/runtime/lifecycle/get-current-agent.mdx index 706eaf1b0fa..e33dd267c40 100644 --- a/src/content/docs/agents/runtime/lifecycle/get-current-agent.mdx +++ b/src/content/docs/agents/runtime/lifecycle/get-current-agent.mdx @@ -320,18 +320,18 @@ The context available depends on how the method was invoked: diff --git a/src/content/docs/agents/runtime/lifecycle/sessions.mdx b/src/content/docs/agents/runtime/lifecycle/sessions.mdx index 84967bb6bde..5bd33e614bf 100644 --- a/src/content/docs/agents/runtime/lifecycle/sessions.mdx +++ b/src/content/docs/agents/runtime/lifecycle/sessions.mdx @@ -906,5 +906,5 @@ By default, storage is in Durable Object SQLite and tables are created lazily on ## Related - [Think](/agents/harnesses/think/) — opinionated chat agent that uses Session for conversation storage via `configureSession()` -- [Chat agents](/agents/communication-channels/chat/chat-agents/) — `AIChatAgent` with its own message persistence layer -- [Store and sync state](/agents/runtime/lifecycle/state/) — `setState()` for simpler key-value persistence +- [Chat agents](/agents/agent-class/communication-channels/chat/chat-agents/) — `AIChatAgent` with its own message persistence layer +- [Store and sync state](/agents/agent-class/state/) — `setState()` for simpler key-value persistence diff --git a/src/content/docs/agents/runtime/operations/configuration.mdx b/src/content/docs/agents/runtime/operations/configuration.mdx index 4fd49eefa80..ab9a592a4f1 100644 --- a/src/content/docs/agents/runtime/operations/configuration.mdx +++ b/src/content/docs/agents/runtime/operations/configuration.mdx @@ -330,7 +330,7 @@ export default defineConfig({ The `agents()` plugin is safe to include even if your project does not use decorators. It only runs the transform on files that contain `@` syntax. -The starter template and all examples include this plugin by default. If you encounter `SyntaxError: Invalid or unexpected token` with decorators, refer to [Callable methods — Troubleshooting](/agents/runtime/lifecycle/callable-methods/#troubleshooting). +The starter template and all examples include this plugin by default. If you encounter `SyntaxError: Invalid or unexpected token` with decorators, refer to [Callable methods — Troubleshooting](/agents/agent-class/callable-methods/#troubleshooting). ## Generating types @@ -939,18 +939,18 @@ Consider converting to the declarative [`exports`](/durable-objects/reference/du diff --git a/src/content/docs/agents/runtime/operations/cross-domain-authentication.mdx b/src/content/docs/agents/runtime/operations/cross-domain-authentication.mdx index 12c801a1785..75270b95c4f 100644 --- a/src/content/docs/agents/runtime/operations/cross-domain-authentication.mdx +++ b/src/content/docs/agents/runtime/operations/cross-domain-authentication.mdx @@ -256,13 +256,13 @@ export class SecureAgent extends Agent { @@ -274,6 +274,6 @@ export class SecureAgent extends Agent { diff --git a/src/content/docs/agents/runtime/operations/observability/tracing.mdx b/src/content/docs/agents/runtime/operations/observability/tracing.mdx index 3e26ae7050f..3ac85830243 100644 --- a/src/content/docs/agents/runtime/operations/observability/tracing.mdx +++ b/src/content/docs/agents/runtime/operations/observability/tracing.mdx @@ -279,6 +279,6 @@ Beginning December 1, 2026, agent traces contribute to account-level ingestion a diff --git a/src/content/docs/agents/tools/ai-search.mdx b/src/content/docs/agents/tools/ai-search.mdx index 964b74449d3..098d2ae90d1 100644 --- a/src/content/docs/agents/tools/ai-search.mdx +++ b/src/content/docs/agents/tools/ai-search.mdx @@ -10,7 +10,7 @@ products: import { LinkCard, TypeScriptExample, WranglerConfig } from "~/components"; -Agents can use [AI Search](/ai-search/) to retrieve relevant information from indexed content and use it to augment [calls to AI models](/agents/runtime/operations/using-ai-models/). AI Search manages the retrieval pipeline for you, including indexing, search, and optional chat completions over your content. +Agents can use [AI Search](/ai-search/) to retrieve relevant information from indexed content and use it to augment [calls to AI models](/agents/agent-class/using-ai-models/). AI Search manages the retrieval pipeline for you, including indexing, search, and optional chat completions over your content. Use AI Search when you want an agent to: diff --git a/src/content/docs/agents/tools/codemode/browser.mdx b/src/content/docs/agents/tools/codemode/browser.mdx index 94d293b855a..8c3f055191f 100644 --- a/src/content/docs/agents/tools/codemode/browser.mdx +++ b/src/content/docs/agents/tools/codemode/browser.mdx @@ -215,4 +215,4 @@ The timeout cannot interrupt a tight synchronous loop such as `while (true) {}`. `createBrowserCodeTool()` excludes any tool whose `needsApproval` value is `true` or a function. Code Mode does not pause iframe execution to request approval for those tools. -Keep approval-gated actions outside the Code Mode descriptor. Register them as standard tools and use the [`useAgentChat()` approval flow](/agents/communication-channels/chat/chat-agents/#tool-approval-human-in-the-loop) instead. +Keep approval-gated actions outside the Code Mode descriptor. Register them as standard tools and use the [`useAgentChat()` approval flow](/agents/agent-class/communication-channels/chat/chat-agents/#tool-approval-human-in-the-loop) instead. diff --git a/src/content/docs/agents/tools/mcp.mdx b/src/content/docs/agents/tools/mcp.mdx index 9f9e9904206..6c718620c09 100644 --- a/src/content/docs/agents/tools/mcp.mdx +++ b/src/content/docs/agents/tools/mcp.mdx @@ -53,7 +53,7 @@ export class ToolAgent extends Agent { -If the server requires OAuth, `addMcpServer()` returns an authentication state and authorization URL. The connection is persisted in the agent's [SQL storage](/agents/runtime/lifecycle/state/). +If the server requires OAuth, `addMcpServer()` returns an authentication state and authorization URL. The connection is persisted in the agent's [SQL storage](/agents/agent-class/state/). ## Configuration diff --git a/src/content/docs/agents/tools/sandbox.mdx b/src/content/docs/agents/tools/sandbox.mdx index 37b18717ff1..95cd36a48e8 100644 --- a/src/content/docs/agents/tools/sandbox.mdx +++ b/src/content/docs/agents/tools/sandbox.mdx @@ -196,7 +196,7 @@ Files in the sandbox last while the container runs. When the container stops, th Use agent state for user-visible progress and small metadata, such as the ID of the latest snapshot. -For long-running sandbox work, pair the sandbox with [durable execution with fibers](/agents/runtime/execution/durable-execution/) or [Workflows](/agents/runtime/execution/run-workflows/) so the agent can recover or report progress if work outlives a single request. +For long-running sandbox work, pair the sandbox with [durable execution with fibers](/agents/agent-class/durable-execution/) or [Workflows](/agents/agent-class/run-workflows/) so the agent can recover or report progress if work outlives a single request. ## Security considerations diff --git a/src/content/docs/ai-search/agent-sdks/agents-sdk.mdx b/src/content/docs/ai-search/agent-sdks/agents-sdk.mdx index 1a4c84fd5df..53fb7a38a81 100644 --- a/src/content/docs/ai-search/agent-sdks/agents-sdk.mdx +++ b/src/content/docs/ai-search/agent-sdks/agents-sdk.mdx @@ -230,7 +230,7 @@ npx wrangler types npm run dev ``` -`AIChatAgent` speaks its chat protocol over a WebSocket, so drive it from a chat client rather than a plain `curl` request. The quickest way is to point a UI built with the Agents SDK [`useAgentChat`](/agents/communication-channels/chat/chat-agents/) hook at your local server. +`AIChatAgent` speaks its chat protocol over a WebSocket, so drive it from a chat client rather than a plain `curl` request. The quickest way is to point a UI built with the Agents SDK [`useAgentChat`](/agents/agent-class/communication-channels/chat/chat-agents/) hook at your local server. Send a message such as `How do I get started?`. On the first message, the agent creates and seeds the instance, so the first response can take a minute or two while the seed document indexes. diff --git a/src/content/docs/ai-search/how-to/human-in-the-loop-knowledge-base.mdx b/src/content/docs/ai-search/how-to/human-in-the-loop-knowledge-base.mdx index 86b86805e66..aeadd67b3fa 100644 --- a/src/content/docs/ai-search/how-to/human-in-the-loop-knowledge-base.mdx +++ b/src/content/docs/ai-search/how-to/human-in-the-loop-knowledge-base.mdx @@ -366,7 +366,7 @@ async () => { `aiSearch.search()` runs immediately. When the program reaches `aiSearch.saveDocument()`, the runtime records the call as pending and pauses the execution before the upload runs. -Your client sends the chat message that starts the run, then drives the approval with the `@callable()` methods. The following script uses the [Agents SDK client](/agents/communication-channels/chat/client-sdk/) to do both. Save it as `client.mjs`, set `HOST` to your deployed Worker, and run it with `node client.mjs`: +Your client sends the chat message that starts the run, then drives the approval with the `@callable()` methods. The following script uses the [Agents SDK client](/agents/agent-class/communication-channels/chat/client-sdk/) to do both. Save it as `client.mjs`, set `HOST` to your deployed Worker, and run it with `node client.mjs`: diff --git a/src/content/docs/ai-search/how-to/talk-to-your-knowledge-base.mdx b/src/content/docs/ai-search/how-to/talk-to-your-knowledge-base.mdx index 2059cb136fd..2832cc2ecb5 100644 --- a/src/content/docs/ai-search/how-to/talk-to-your-knowledge-base.mdx +++ b/src/content/docs/ai-search/how-to/talk-to-your-knowledge-base.mdx @@ -18,7 +18,7 @@ import { } from "~/components"; import AiSearchVoiceDiagram from "~/components/AiSearchVoiceDiagram.astro"; -This tutorial builds a voice agent that you can talk to and that answers out loud from your [AI Search](/ai-search/) knowledge base. It uses the [Cloudflare Agents](/agents/) [`@cloudflare/voice`](/agents/communication-channels/voice/) package for the speech pipeline, and AI Search as the agent's knowledge base, exposed as a retrieval tool the agent's model calls. +This tutorial builds a voice agent that you can talk to and that answers out loud from your [AI Search](/ai-search/) knowledge base. It uses the [Cloudflare Agents](/agents/) [`@cloudflare/voice`](/agents/agent-class/communication-channels/voice/) package for the speech pipeline, and AI Search as the agent's knowledge base, exposed as a retrieval tool the agent's model calls. **What you will build:** A voice agent that transcribes your speech, calls AI Search to retrieve relevant content from your indexed knowledge base, generates a grounded answer, and speaks it back. @@ -53,7 +53,7 @@ Install the voice package: -The [`@cloudflare/voice`](/agents/communication-channels/voice/) package provides the `withVoice` mixin and the Workers AI providers (`WorkersAIFluxSTT` and `WorkersAITTS`). For a full walkthrough of the voice agent itself, including the browser client, refer to the [Voice agent example](/agents/examples/voice-agent/). +The [`@cloudflare/voice`](/agents/agent-class/communication-channels/voice/) package provides the `withVoice` mixin and the Workers AI providers (`WorkersAIFluxSTT` and `WorkersAITTS`). For a full walkthrough of the voice agent itself, including the browser client, refer to the [Voice agent example](/agents/examples/voice-agent/). ## 2. Add the AI Search binding @@ -199,7 +199,7 @@ Use a Workers AI model that supports function calling so the model can call the ## 4. Build the client -Replace `src/client.tsx` with a React component that uses the [`useVoiceAgent`](/agents/communication-channels/voice/) hook. The hook manages the microphone, the WebSocket connection to your agent, audio playback, and interrupt detection, so the component only needs to render controls. Set `agent` to your agent class name, `TalkToDocs`. +Replace `src/client.tsx` with a React component that uses the [`useVoiceAgent`](/agents/agent-class/communication-channels/voice/) hook. The hook manages the microphone, the WebSocket connection to your agent, audio playback, and interrupt detection, so the component only needs to render controls. Set `agent` to your agent class name, `TalkToDocs`. ```tsx title="src/client.tsx" import { useVoiceAgent } from "@cloudflare/voice/react"; @@ -292,7 +292,7 @@ This tutorial builds a single-user voice agent. If you instead need several peop diff --git a/src/content/docs/workers/demos/chatgpt-app.mdx b/src/content/docs/workers/demos/chatgpt-app.mdx index e5ccd8ba0d7..9ea01e4e048 100644 --- a/src/content/docs/workers/demos/chatgpt-app.mdx +++ b/src/content/docs/workers/demos/chatgpt-app.mdx @@ -868,7 +868,7 @@ Now that you have a working ChatGPT App, you can: diff --git a/src/content/docs/workflows/get-started/durable-agents.mdx b/src/content/docs/workflows/get-started/durable-agents.mdx index 97efadb5912..56115326a7d 100644 --- a/src/content/docs/workflows/get-started/durable-agents.mdx +++ b/src/content/docs/workflows/get-started/durable-agents.mdx @@ -649,7 +649,7 @@ Agent class names are automatically converted to kebab-case for URLs (`ResearchA diff --git a/src/content/partials/agents/common-integration-patterns.mdx b/src/content/partials/agents/common-integration-patterns.mdx index c596443ec91..5ee7dd47d3f 100644 --- a/src/content/partials/agents/common-integration-patterns.mdx +++ b/src/content/partials/agents/common-integration-patterns.mdx @@ -49,7 +49,7 @@ const agentResponse = await routeAgentRequest(request, env, { -Refer to [Routing](/agents/runtime/communication/routing/) for more options including CORS, custom instance naming, and location hints. +Refer to [Routing](/agents/agent-class/routing/) for more options including CORS, custom instance naming, and location hints. ### Accessing agents from server code diff --git a/src/content/partials/agents/next-steps.mdx b/src/content/partials/agents/next-steps.mdx index b1a384f100f..42c4780fb04 100644 --- a/src/content/partials/agents/next-steps.mdx +++ b/src/content/partials/agents/next-steps.mdx @@ -12,46 +12,46 @@ Now that you have a working agent, explore these topics: | Learn how to | Refer to | | ------------------------ | --------------------------------------------------------- | -| Add AI/LLM capabilities | [Using AI models](/agents/runtime/operations/using-ai-models/) | +| Add AI/LLM capabilities | [Using AI models](/agents/agent-class/using-ai-models/) | | Expose tools via MCP | [MCP servers](/agents/model-context-protocol/apis/handler-api/) | -| Run background tasks | [Schedule tasks](/agents/runtime/execution/schedule-tasks/) | -| Handle emails | [Email routing](/agents/communication-channels/email/) | -| Use Cloudflare Workflows | [Run Workflows](/agents/runtime/execution/run-workflows/) | +| Run background tasks | [Schedule tasks](/agents/agent-class/schedule-tasks/) | +| Handle emails | [Email routing](/agents/agent-class/communication-channels/email/) | +| Use Cloudflare Workflows | [Run Workflows](/agents/agent-class/run-workflows/) | ### Explore more \ No newline at end of file From c1c13d18aabd8d3c72181ea1f90bc3349a78ee39 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 27 Oct 2026 11:14:35 +0000 Subject: [PATCH 02/11] [Agents] Address style review: relative links, refer to, serial comma, split sentences --- src/content/changelog/agents/2025-03-18-npm-i-agents.mdx | 2 +- .../agents/2025-11-26-agents-resumable-streaming.mdx | 4 ++-- .../agents/2026-02-03-agents-workflows-integration.mdx | 2 +- src/content/changelog/agents/2026-02-17-agents-sdk-v0.5.0.mdx | 4 ++-- .../communication-channels/chat/autonomous-responses.mdx | 2 +- .../agents/concepts/agentic-patterns/long-running-agents.mdx | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/content/changelog/agents/2025-03-18-npm-i-agents.mdx b/src/content/changelog/agents/2025-03-18-npm-i-agents.mdx index 3bd3242dead..b5b97ed1dae 100644 --- a/src/content/changelog/agents/2025-03-18-npm-i-agents.mdx +++ b/src/content/changelog/agents/2025-03-18-npm-i-agents.mdx @@ -127,7 +127,7 @@ You can use the starter as a template for your own Agents projects: open up `src We've heard your feedback on the Agents SDK documentation, and we're shipping more API reference material and usage examples, including: - Expanded [API reference documentation](/agents/runtime/), covering the methods and properties exposed by the Agents SDK, as well as more usage examples. -- More [Client API](/agents/agent-class/#client-api) documentation that documents `useAgent`, `useAgentChat` and the new `@unstable_callable` RPC decorator exposed by the SDK. +- More [Client API](/agents/agent-class/#client-api) documentation that documents `useAgent`, `useAgentChat`, and the new `@unstable_callable` RPC decorator exposed by the SDK. - New documentation on how to [route requests to agents](/agents/agent-class/routing/) and (optionally) authenticate clients before they connect to your Agents. Note that the Agents SDK is continually growing: the type definitions included in the SDK will always include the latest APIs exposed by the `agents` package. diff --git a/src/content/changelog/agents/2025-11-26-agents-resumable-streaming.mdx b/src/content/changelog/agents/2025-11-26-agents-resumable-streaming.mdx index 560b2eeac6d..4c19d25c918 100644 --- a/src/content/changelog/agents/2025-11-26-agents-resumable-streaming.mdx +++ b/src/content/changelog/agents/2025-11-26-agents-resumable-streaming.mdx @@ -24,7 +24,7 @@ Streams are maintained across page refreshes, broken connections, and syncing ac ### Other improvements - Default JSON schema validator added to MCP client -- [Schedules](https://developers.cloudflare.com/agents/agent-class/schedule-tasks/) can now safely destroy the agent +- [Schedules](/agents/agent-class/schedule-tasks/) can now safely destroy the agent ### MCP client API improvements @@ -60,7 +60,7 @@ MCP discovery fetches the available tools, prompts, and resources from an MCP se ### Bug fixes -- Fixed a bug where [schedules](https://developers.cloudflare.com/agents/agent-class/schedule-tasks/) meant to fire immediately with this.schedule(0, ...) or `this.schedule(new Date(), ...)` would not fire +- Fixed a bug where [schedules](/agents/agent-class/schedule-tasks/) meant to fire immediately with this.schedule(0, ...) or `this.schedule(new Date(), ...)` would not fire - Fixed an issue where schedules that took longer than 30 seconds would occasionally time out - Fixed SSE transport now properly forwards session IDs and request headers - Fixed AI SDK stream events conversion to UIMessageStreamPart diff --git a/src/content/changelog/agents/2026-02-03-agents-workflows-integration.mdx b/src/content/changelog/agents/2026-02-03-agents-workflows-integration.mdx index 7fd6157f3c0..20fac12d9a9 100644 --- a/src/content/changelog/agents/2026-02-03-agents-workflows-integration.mdx +++ b/src/content/changelog/agents/2026-02-03-agents-workflows-integration.mdx @@ -175,4 +175,4 @@ To update to the latest version: npm i agents@latest ``` -For the complete Workflows API reference and patterns, see [Run Workflows](/agents/agent-class/run-workflows/). +For the complete Workflows API reference and patterns, refer to [Run Workflows](/agents/agent-class/run-workflows/). diff --git a/src/content/changelog/agents/2026-02-17-agents-sdk-v0.5.0.mdx b/src/content/changelog/agents/2026-02-17-agents-sdk-v0.5.0.mdx index 01093055962..abb8da7b93a 100644 --- a/src/content/changelog/agents/2026-02-17-agents-sdk-v0.5.0.mdx +++ b/src/content/changelog/agents/2026-02-17-agents-sdk-v0.5.0.mdx @@ -73,7 +73,7 @@ class MyAgent extends Agent { Connections with protocol messages disabled still fully participate in RPC and regular messaging. Use `isConnectionProtocolEnabled(connection)` to check a connection's status at any time. The flag persists across Durable Object hibernation. -See [Protocol messages](/agents/agent-class/protocol-messages/) for full documentation. +Refer to [Protocol messages](/agents/agent-class/protocol-messages/) for full documentation. ## `@cloudflare/ai-chat` v0.1.0 @@ -81,7 +81,7 @@ The first stable release of `@cloudflare/ai-chat` ships alongside this release w Key new features: -- **Data parts** — Attach typed JSON blobs (`data-*`) to messages alongside text. Supports reconciliation (type+id updates in-place), append, and transient parts (ephemeral via `onData` callback). See [Data parts](/agents/agent-class/communication-channels/chat/chat-agents/#data-parts). +- **Data parts** — Attach typed JSON blobs (`data-*`) to messages alongside text. Supports reconciliation (type+id updates in-place), append, and transient parts (ephemeral via `onData` callback). Refer to [Data parts](/agents/agent-class/communication-channels/chat/chat-agents/#data-parts). - **Tool approval persistence** — The `needsApproval` approval UI now survives page refresh and DO hibernation. The streaming message is persisted to SQLite when a tool enters `approval-requested` state. - **`maxPersistedMessages`** — Cap SQLite message storage with automatic oldest-message deletion. - **`body` option on `useAgentChat`** — Send custom data with every request (static or dynamic). diff --git a/src/content/docs/agents/agent-class/communication-channels/chat/autonomous-responses.mdx b/src/content/docs/agents/agent-class/communication-channels/chat/autonomous-responses.mdx index 52768f99752..cb344e872e0 100644 --- a/src/content/docs/agents/agent-class/communication-channels/chat/autonomous-responses.mdx +++ b/src/content/docs/agents/agent-class/communication-channels/chat/autonomous-responses.mdx @@ -66,7 +66,7 @@ return Response.json({ `submitMessages()` stores pending work first and appends the messages to the conversation Session only when the submission starts executing. It accepts serializable `UIMessage[]` values, not the function form supported by `saveMessages((messages) => ...)`. -Use [`startFiber()`](/agents/agent-class/durable-execution/#startfiber) outside Think when the durable unit is a surrounding application job, such as accepting a webhook once, restoring provider state, posting a visible reply, and recording recovery policy. `submitMessages()` owns Think's conversation admission; managed fibers own external side effects around that turn. +Use [`startFiber()`](/agents/agent-class/durable-execution/#startfiber) outside Think when the durable unit is a surrounding application job, such as accepting a webhook once, restoring provider state, posting a visible reply, and recording recovery policy. `submitMessages()` owns Think's conversation admission. Managed fibers own external side effects around that turn. For the full Think API, refer to [`submitMessages()`](/agents/harnesses/think/programmatic-submissions/#submitmessages). ### When to use `saveMessages` vs `onChatResponse` diff --git a/src/content/docs/agents/concepts/agentic-patterns/long-running-agents.mdx b/src/content/docs/agents/concepts/agentic-patterns/long-running-agents.mdx index 32a33b9c9af..8e99398d8fc 100644 --- a/src/content/docs/agents/concepts/agentic-patterns/long-running-agents.mdx +++ b/src/content/docs/agents/concepts/agentic-patterns/long-running-agents.mdx @@ -229,7 +229,7 @@ An agent can be evicted at any time — a deploy, a platform restart, or hitting [`runFiber()`](/agents/agent-class/durable-execution/) provides crash-recoverable execution. It persists a row in SQLite for the duration of the work, and lets you `stash()` intermediate state. If the agent is evicted, the fiber row survives, and `onFiberRecovered()` is called on the next activation. -Use [`startFiber()`](/agents/agent-class/durable-execution/#startfiber) when the important boundary is durable acceptance. It adds an idempotency key, retained status records, inspection, cancellation, and cleanup on top of the same fiber machinery. By default it returns after acceptance; pass `waitForCompletion: true` when the request should stay open until the accepted job reaches a terminal status. This is a good fit for webhooks where the provider may retry delivery and the agent must avoid starting duplicate visible side effects. +Use [`startFiber()`](/agents/agent-class/durable-execution/#startfiber) when the important boundary is durable acceptance. It adds an idempotency key, retained status records, inspection, cancellation, and cleanup on top of the same fiber machinery. By default it returns after acceptance. Pass `waitForCompletion: true` when the request should stay open until the accepted job reaches a terminal status. This is a good fit for webhooks where the provider may retry delivery and the agent must avoid starting duplicate visible side effects. ```ts export class ProjectManager extends Agent { From efae3578af97788ba3b13e1de8a04ebf907e8790 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 27 Oct 2026 11:10:55 +0000 Subject: [PATCH 03/11] [Agents] Rewrite the core Agents docs around harnesses --- public/__redirects | 24 +- src/components/cf/AgentsPlatformDiagram.astro | 38 +- src/content/agent-setup/tracing.md | 2 +- .../agents/2025-03-18-npm-i-agents.mdx | 2 +- .../agents/2026-03-02-agents-sdk-v0.7.0.mdx | 2 +- .../agents/2026-05-13-agents-sdk-v0.12.4.mdx | 2 +- .../agents/2026-06-02-agents-sdk-v0.14.0.mdx | 2 +- .../agents/2026-08-04-agent-tracing.mdx | 2 +- .../agents/2026-10-13-persistent-browser.mdx | 2 +- src/content/docs/agent-memory/get-started.mdx | 4 +- .../agent-class/add-to-existing-project.mdx | 352 +++++++++++ .../chat/client-sdk.mdx | 2 +- .../get-current-agent.mdx | 4 +- .../docs/agents/agent-class/get-started.mdx | 245 ++++++++ src/content/docs/agents/agent-class/index.mdx | 71 ++- .../docs/agents/agent-class/internals.mdx | 150 ++--- .../docs/agents/agent-class/routing.mdx | 8 +- .../docs/agents/agent-class/websockets.mdx | 2 +- .../docs/agents/capabilities/index.mdx | 344 +++++++++++ .../lifecycle => capabilities}/sessions.mdx | 0 .../conversation-state-and-memory.mdx | 580 +++--------------- .../docs/agents/concepts/what-are-agents.mdx | 18 +- src/content/docs/agents/examples/index.mdx | 4 +- .../add-to-existing-project.mdx | 380 ++++-------- .../docs/agents/getting-started/index.mdx | 20 +- .../agents/getting-started/quick-start.mdx | 307 +++++---- src/content/docs/agents/harnesses/ai-sdk.mdx | 106 ++++ .../docs/agents/harnesses/container.mdx | 144 +++++ src/content/docs/agents/harnesses/index.mdx | 127 ++-- .../docs/agents/harnesses/opencode.mdx | 7 +- .../docs/agents/harnesses/pi/extensions.mdx | 2 +- .../docs/agents/harnesses/pi/index.mdx | 2 +- .../agents/harnesses/think/configuration.mdx | 10 +- .../harnesses/think/getting-started.mdx | 4 +- .../docs/agents/harnesses/think/index.mdx | 10 +- .../docs/agents/harnesses/think/recovery.mdx | 8 +- .../docs/agents/harnesses/think/tools.mdx | 2 +- src/content/docs/agents/index.mdx | 57 +- .../agents/lifecycle/build-a-capability.mdx | 208 +++++++ .../agents/lifecycle/get-current-agent.mdx | 76 +++ src/content/docs/agents/lifecycle/index.mdx | 213 +++++++ .../docs/agents/lifecycle/job-queue.mdx | 126 ++++ .../model-context-protocol/apis/agent-api.mdx | 4 +- src/content/docs/agents/models/index.mdx | 2 +- .../operations => platform}/configuration.mdx | 2 +- .../cross-domain-authentication.mdx | 2 +- src/content/docs/agents/platform/index.mdx | 4 +- .../observability/diagnostics-channels.mdx | 4 +- .../observability/index.mdx | 2 +- .../observability/tracing.mdx | 14 +- .../docs/agents/runtime/execution/index.mdx | 12 - src/content/docs/agents/runtime/index.mdx | 14 - .../docs/agents/runtime/lifecycle/index.mdx | 12 - .../docs/agents/runtime/operations/index.mdx | 12 - .../execution => tools}/agent-skills.mdx | 2 +- src/content/docs/agents/tools/browser.mdx | 2 +- src/content/docs/agents/tools/index.mdx | 2 +- .../partials/agents/agent-class-legacy.mdx | 9 + 58 files changed, 2525 insertions(+), 1243 deletions(-) create mode 100644 src/content/docs/agents/agent-class/add-to-existing-project.mdx rename src/content/docs/agents/{runtime/lifecycle => agent-class}/get-current-agent.mdx (99%) create mode 100644 src/content/docs/agents/agent-class/get-started.mdx create mode 100644 src/content/docs/agents/capabilities/index.mdx rename src/content/docs/agents/{runtime/lifecycle => capabilities}/sessions.mdx (100%) create mode 100644 src/content/docs/agents/harnesses/ai-sdk.mdx create mode 100644 src/content/docs/agents/harnesses/container.mdx create mode 100644 src/content/docs/agents/lifecycle/build-a-capability.mdx create mode 100644 src/content/docs/agents/lifecycle/get-current-agent.mdx create mode 100644 src/content/docs/agents/lifecycle/index.mdx create mode 100644 src/content/docs/agents/lifecycle/job-queue.mdx rename src/content/docs/agents/{runtime/operations => platform}/configuration.mdx (99%) rename src/content/docs/agents/{runtime/operations => platform}/cross-domain-authentication.mdx (99%) rename src/content/docs/agents/{runtime/operations => platform}/observability/diagnostics-channels.mdx (99%) rename src/content/docs/agents/{runtime/operations => platform}/observability/index.mdx (93%) rename src/content/docs/agents/{runtime/operations => platform}/observability/tracing.mdx (94%) delete mode 100644 src/content/docs/agents/runtime/execution/index.mdx delete mode 100644 src/content/docs/agents/runtime/index.mdx delete mode 100644 src/content/docs/agents/runtime/lifecycle/index.mdx delete mode 100644 src/content/docs/agents/runtime/operations/index.mdx rename src/content/docs/agents/{runtime/execution => tools}/agent-skills.mdx (99%) create mode 100644 src/content/partials/agents/agent-class-legacy.mdx diff --git a/public/__redirects b/public/__redirects index fae3f3de5b3..39c4396c09e 100644 --- a/public/__redirects +++ b/public/__redirects @@ -240,6 +240,18 @@ /aegis/analytics/ /smart-shield/configuration/dedicated-egress-ips/ips-utilization/ 301 # agents +/agents/runtime/ /agents/lifecycle/ 301 +/agents/runtime/execution/ /agents/agent-class/ 301 +/agents/runtime/execution/agent-skills/ /agents/tools/agent-skills/ 301 +/agents/runtime/lifecycle/ /agents/lifecycle/ 301 +/agents/runtime/lifecycle/get-current-agent/ /agents/lifecycle/get-current-agent/ 301 +/agents/runtime/lifecycle/sessions/ /agents/capabilities/sessions/ 301 +/agents/runtime/operations/ /agents/platform/ 301 +/agents/runtime/operations/configuration/ /agents/platform/configuration/ 301 +/agents/runtime/operations/cross-domain-authentication/ /agents/platform/cross-domain-authentication/ 301 +/agents/runtime/operations/observability/ /agents/platform/observability/ 301 +/agents/runtime/operations/observability/diagnostics-channels/ /agents/platform/observability/diagnostics-channels/ 301 +/agents/runtime/operations/observability/tracing/ /agents/platform/observability/tracing/ 301 /agents/communication-channels/ /agents/agent-class/communication-channels/ 301 /agents/communication-channels/chat/ /agents/agent-class/communication-channels/chat/ 301 /agents/communication-channels/chat/autonomous-responses/ /agents/agent-class/communication-channels/chat/autonomous-responses/ 301 @@ -298,7 +310,7 @@ /agents/concepts/human-in-the-loop/ /agents/concepts/agentic-patterns/human-in-the-loop/ 301 /agents/concepts/long-running-agents/ /agents/concepts/agentic-patterns/long-running-agents/ 301 /agents/api-reference/agents-api/ /agents/agent-class/ 301 -/agents/api-reference/ /agents/runtime/ 301 +/agents/api-reference/ /agents/lifecycle/ 301 /agents/api-reference/chat-sdk/ /agents/agent-class/communication-channels/chat-sdk/ 301 /agents/runtime/execution/chat-sdk/ /agents/agent-class/communication-channels/chat-sdk/ 301 /agents/api-reference/agent-tools/ /agents/agent-class/agent-tools/ 301 @@ -307,15 +319,15 @@ /agents/api-reference/chat-agents/ /agents/agent-class/communication-channels/chat/chat-agents/ 301 /agents/api-reference/client-sdk/ /agents/agent-class/communication-channels/chat/client-sdk/ 301 /agents/api-reference/codemode/ /agents/tools/codemode/api-reference/ 301 -/agents/api-reference/configuration/ /agents/runtime/operations/configuration/ 301 +/agents/api-reference/configuration/ /agents/platform/configuration/ 301 /agents/api-reference/durable-execution/ /agents/agent-class/durable-execution/ 301 /agents/api-reference/email/ /agents/agent-class/communication-channels/email/ 301 -/agents/api-reference/get-current-agent/ /agents/runtime/lifecycle/get-current-agent/ 301 +/agents/api-reference/get-current-agent/ /agents/lifecycle/get-current-agent/ 301 /agents/api-reference/http-sse/ /agents/agent-class/http-sse/ 301 /agents/api-reference/mcp-agent-api/ /agents/model-context-protocol/apis/agent-api/ 301 /agents/api-reference/mcp-client-api/ /agents/model-context-protocol/apis/client-api/ 301 /agents/api-reference/mcp-handler-api/ /agents/model-context-protocol/apis/handler-api/ 301 -/agents/api-reference/observability/ /agents/runtime/operations/observability/ 301 +/agents/api-reference/observability/ /agents/platform/observability/ 301 /agents/api-reference/protocol-messages/ /agents/agent-class/protocol-messages/ 301 /agents/api-reference/queue-tasks/ /agents/agent-class/queue-tasks/ 301 /agents/api-reference/rag/ /agents/tools/ai-search/ 301 @@ -324,7 +336,7 @@ /agents/api-reference/routing/ /agents/agent-class/routing/ 301 /agents/api-reference/run-workflows/ /agents/agent-class/run-workflows/ 301 /agents/api-reference/schedule-tasks/ /agents/agent-class/schedule-tasks/ 301 -/agents/api-reference/sessions/ /agents/runtime/lifecycle/sessions/ 301 +/agents/api-reference/sessions/ /agents/capabilities/sessions/ 301 /agents/api-reference/store-and-sync-state/ /agents/agent-class/state/ 301 /agents/api-reference/sub-agents/ /agents/agent-class/sub-agents/ 301 /agents/api-reference/using-ai-models/ /agents/agent-class/using-ai-models/ 301 @@ -337,7 +349,7 @@ /agents/guides/chatgpt-app/ /workers/demos/chatgpt-app/ 301 /agents/communication-channels/chat/chatgpt-app/ /workers/demos/chatgpt-app/ 301 /agents/guides/connect-mcp-client/ /agents/model-context-protocol/guides/connect-mcp-client/ 301 -/agents/guides/cross-domain-authentication/ /agents/runtime/operations/cross-domain-authentication/ 301 +/agents/guides/cross-domain-authentication/ /agents/platform/cross-domain-authentication/ 301 /agents/guides/human-in-the-loop/ /agents/concepts/agentic-patterns/human-in-the-loop/ 301 /agents/guides/oauth-mcp-client/ /agents/model-context-protocol/guides/oauth-mcp-client/ 301 /agents/guides/push-notifications/ /agents/agent-class/communication-channels/webhooks/push-notifications/ 301 diff --git a/src/components/cf/AgentsPlatformDiagram.astro b/src/components/cf/AgentsPlatformDiagram.astro index 44394f8359f..ec81fc77e4b 100644 --- a/src/components/cf/AgentsPlatformDiagram.astro +++ b/src/components/cf/AgentsPlatformDiagram.astro @@ -28,12 +28,12 @@ const channels = [ ]; const runtime = [ - { label: "State", href: "/agents/agent-class/state/" }, - { label: "Sessions", href: "/agents/runtime/lifecycle/sessions/" }, - { label: "Routing", href: "/agents/agent-class/routing/" }, - { label: "WebSockets", href: "/agents/agent-class/websockets/" }, - { label: "Scheduling", href: "/agents/agent-class/schedule-tasks/" }, - { label: "Fibers", href: "/agents/agent-class/durable-execution/" }, + { label: "MCP client", href: "/agents/capabilities/#mcp-client" }, + { label: "Scheduler", href: "/agents/capabilities/#scheduler" }, + { label: "Queue", href: "/agents/capabilities/#queue" }, + { label: "WebSockets", href: "/agents/capabilities/#websockets" }, + { label: "Sessions", href: "/agents/capabilities/#sessions" }, + { label: "Tasks", href: "/agents/capabilities/#tasks" }, ]; const tools = [ @@ -94,20 +94,27 @@ const tools = [

Controls planning, tool use, and response flow.

- Pi harness - Build-your-own agent + Pi + OpenCode + AI SDK + Containers + Project Think + + Build your own +
-
Agents SDK runtime
+
Durable Object and capabilities

- Durable identity, state, connections, scheduling, and recovery. + The Lifecycle keeps the harness running. Plug in what else it + needs.

- - Agent class + + Lifecycle
{runtime.map((item) => ( @@ -132,10 +139,7 @@ const tools = [
- + Observability Logs · metrics · traces @@ -387,7 +391,7 @@ const tools = [ .section-row { display: grid; - grid-template-columns: 1fr; + grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; margin-top: 9px; } diff --git a/src/content/agent-setup/tracing.md b/src/content/agent-setup/tracing.md index de3aacbb049..0bb863bca58 100644 --- a/src/content/agent-setup/tracing.md +++ b/src/content/agent-setup/tracing.md @@ -167,7 +167,7 @@ State any validation command that could not run and why. ## Resources -- Agent tracing and framework setup: `https://developers.cloudflare.com/agents/runtime/operations/observability/tracing/` +- Agent tracing and framework setup: `https://developers.cloudflare.com/agents/platform/observability/tracing/` - Workers tracing and sampling: `https://developers.cloudflare.com/workers/observability/traces/` These instructions are published at `https://developers.cloudflare.com/agent-setup/tracing.md` so you can re-verify their authenticity at any time. diff --git a/src/content/changelog/agents/2025-03-18-npm-i-agents.mdx b/src/content/changelog/agents/2025-03-18-npm-i-agents.mdx index b5b97ed1dae..05cab32d9a8 100644 --- a/src/content/changelog/agents/2025-03-18-npm-i-agents.mdx +++ b/src/content/changelog/agents/2025-03-18-npm-i-agents.mdx @@ -126,7 +126,7 @@ You can use the starter as a template for your own Agents projects: open up `src We've heard your feedback on the Agents SDK documentation, and we're shipping more API reference material and usage examples, including: -- Expanded [API reference documentation](/agents/runtime/), covering the methods and properties exposed by the Agents SDK, as well as more usage examples. +- Expanded [API reference documentation](/agents/agent-class/), covering the methods and properties exposed by the Agents SDK, as well as more usage examples. - More [Client API](/agents/agent-class/#client-api) documentation that documents `useAgent`, `useAgentChat`, and the new `@unstable_callable` RPC decorator exposed by the SDK. - New documentation on how to [route requests to agents](/agents/agent-class/routing/) and (optionally) authenticate clients before they connect to your Agents. diff --git a/src/content/changelog/agents/2026-03-02-agents-sdk-v0.7.0.mdx b/src/content/changelog/agents/2026-03-02-agents-sdk-v0.7.0.mdx index 0c4750f999a..065e9391311 100644 --- a/src/content/changelog/agents/2026-03-02-agents-sdk-v0.7.0.mdx +++ b/src/content/changelog/agents/2026-03-02-agents-sdk-v0.7.0.mdx @@ -72,7 +72,7 @@ export default { The custom `Observability` override interface is still supported for users who need to filter or forward events to external services. -For the full event reference, refer to the [Diagnostics channels documentation](/agents/runtime/operations/observability/diagnostics-channels/). +For the full event reference, refer to the [Diagnostics channels documentation](/agents/platform/observability/diagnostics-channels/). ## `keepAlive()` and `keepAliveWhile()` diff --git a/src/content/changelog/agents/2026-05-13-agents-sdk-v0.12.4.mdx b/src/content/changelog/agents/2026-05-13-agents-sdk-v0.12.4.mdx index 6af6f993359..2a546261bc2 100644 --- a/src/content/changelog/agents/2026-05-13-agents-sdk-v0.12.4.mdx +++ b/src/content/changelog/agents/2026-05-13-agents-sdk-v0.12.4.mdx @@ -125,4 +125,4 @@ To update to the latest version: npm i agents@latest @cloudflare/ai-chat@latest @cloudflare/think@latest @cloudflare/voice@latest ``` -Refer to the [Agents API reference](/agents/runtime/) and [Chat agents documentation](/agents/agent-class/communication-channels/chat/chat-agents/) for more information. +Refer to the [Agents API reference](/agents/lifecycle/) and [Chat agents documentation](/agents/agent-class/communication-channels/chat/chat-agents/) for more information. diff --git a/src/content/changelog/agents/2026-06-02-agents-sdk-v0.14.0.mdx b/src/content/changelog/agents/2026-06-02-agents-sdk-v0.14.0.mdx index 78abe7f2913..71a610a2f2a 100644 --- a/src/content/changelog/agents/2026-06-02-agents-sdk-v0.14.0.mdx +++ b/src/content/changelog/agents/2026-06-02-agents-sdk-v0.14.0.mdx @@ -160,4 +160,4 @@ To update to the latest version: -Refer to the [Agents API reference](/agents/runtime/) and [Chat agents documentation](/agents/agent-class/communication-channels/chat/chat-agents/) for more information. +Refer to the [Agents API reference](/agents/lifecycle/) and [Chat agents documentation](/agents/agent-class/communication-channels/chat/chat-agents/) for more information. diff --git a/src/content/changelog/agents/2026-08-04-agent-tracing.mdx b/src/content/changelog/agents/2026-08-04-agent-tracing.mdx index 8973f10fc2f..59ef0618d69 100644 --- a/src/content/changelog/agents/2026-08-04-agent-tracing.mdx +++ b/src/content/changelog/agents/2026-08-04-agent-tracing.mdx @@ -46,4 +46,4 @@ Think and Flue agents are instrumented automatically. For direct AI SDK calls, w Check out the full blog post: [Introducing: Cloudflare Agents](https://blog.cloudflare.com/agents-on-cloudflare). -For setup details, refer to the [Agent tracing documentation](/agents/runtime/operations/observability/tracing/). +For setup details, refer to the [Agent tracing documentation](/agents/platform/observability/tracing/). diff --git a/src/content/changelog/agents/2026-10-13-persistent-browser.mdx b/src/content/changelog/agents/2026-10-13-persistent-browser.mdx index f17c9ccb6c6..8c778e827a9 100644 --- a/src/content/changelog/agents/2026-10-13-persistent-browser.mdx +++ b/src/content/changelog/agents/2026-10-13-persistent-browser.mdx @@ -10,7 +10,7 @@ The [Cloudflare Agents SDK](/agents/) now provides a `browserTool` that gives yo When you give `browserTool` to your agent, the model writes code that drives the browser through the [Chrome DevTools Protocol (CDP)](/browser-run/cdp/). Screenshots return as images that an image-capable model can inspect and your UI can display. -To use `browserTool`, make sure you have [Agents SDK v0.28.0](https://github.com/cloudflare/agents/releases/tag/agents%400.28.0) or greater installed. Create a `Browser`, register it with your object's [Lifecycle](/agents/runtime/lifecycle/), and add the tool to a [Pi Durable](/agents/harnesses/pi/) [extension registry](/agents/harnesses/pi/extensions/) before opening the harness: +To use `browserTool`, make sure you have [Agents SDK v0.28.0](https://github.com/cloudflare/agents/releases/tag/agents%400.28.0) or greater installed. Create a `Browser`, register it with your object's [Lifecycle](/agents/lifecycle/), and add the tool to a [Pi Durable](/agents/harnesses/pi/) [extension registry](/agents/harnesses/pi/extensions/) before opening the harness: diff --git a/src/content/docs/agent-memory/get-started.mdx b/src/content/docs/agent-memory/get-started.mdx index ac18713e71f..0e98992aebf 100644 --- a/src/content/docs/agent-memory/get-started.mdx +++ b/src/content/docs/agent-memory/get-started.mdx @@ -18,7 +18,7 @@ import { Add Agent Memory to an agent so it can recall durable context across conversations. -This guide uses the [Agents SDK](/agents/) and its [Session API](/agents/runtime/lifecycle/sessions/) to expose memory recall as a model-callable tool. The same pattern applies if you use another agent framework: store memories with `ingest()` or `remember()`, expose `recall()` through one of your agent's tools, and use the system prompt to tell the model when to search memory. +This guide uses the [Agents SDK](/agents/) and its [Session API](/agents/capabilities/sessions/) to expose memory recall as a model-callable tool. The same pattern applies if you use another agent framework: store memories with `ingest()` or `remember()`, expose `recall()` through one of your agent's tools, and use the system prompt to tell the model when to search memory. ## Prerequisites @@ -104,7 +104,7 @@ Generate local TypeScript types for your bindings: The model cannot use memory just because your application has a memory binding. You need to expose recall through a tool and instruct the model when to call it. -With the Agents SDK [Session API](/agents/runtime/lifecycle/sessions/), add a searchable context provider. Session turns the provider's `search()` method into a `search_context` tool for the model. +With the Agents SDK [Session API](/agents/capabilities/sessions/), add a searchable context provider. Session turns the provider's `search()` method into a `search_context` tool for the model. Create `src/server.ts` and add the recall setup: diff --git a/src/content/docs/agents/agent-class/add-to-existing-project.mdx b/src/content/docs/agents/agent-class/add-to-existing-project.mdx new file mode 100644 index 00000000000..2e0b9cf9528 --- /dev/null +++ b/src/content/docs/agents/agent-class/add-to-existing-project.mdx @@ -0,0 +1,352 @@ +--- +title: Add an Agent to an existing project +description: Add the Agent class to an existing Cloudflare Workers project with state management and real-time connections. +pcx_content_type: how-to +sidebar: + order: 0.5 + label: Add to an existing project +products: + - agents +--- + +import { + PackageManagers, + TypeScriptExample, + WranglerConfig, + Render, +} from "~/components"; + + + +This guide shows how to add agents to an existing Cloudflare Workers project. If you are starting fresh, refer to [Building a chat agent](/agents/examples/chat-agent/) instead. + +## Prerequisites + +- An existing Cloudflare Workers project with a Wrangler configuration file +- Node.js 18 or newer + +## 1. Install the package + + + +For React applications, no additional packages are needed — React bindings are included. + +For Hono applications: + + + +## 2. Create an Agent + +Create a new file for your agent (for example, `src/agents/counter.ts`): + + + +```ts +import { Agent, callable } from "agents"; + +export type CounterState = { + count: number; +}; + +export class CounterAgent extends Agent { + initialState: CounterState = { count: 0 }; + + @callable() + increment() { + this.setState({ count: this.state.count + 1 }); + return this.state.count; + } + + @callable() + decrement() { + this.setState({ count: this.state.count - 1 }); + return this.state.count; + } +} +``` + + + +## 3. Update Wrangler configuration + +Add the Durable Object binding and migration: + + + +```jsonc +{ + "name": "my-existing-project", + "main": "src/index.ts", + "upload_source_maps": true, + "compatibility_date": "$today", + "compatibility_flags": ["nodejs_compat"], + + "durable_objects": { + "bindings": [ + { + "name": "CounterAgent", + "class_name": "CounterAgent", + }, + ], + }, + + "migrations": [ + { + "tag": "v1", + "new_sqlite_classes": ["CounterAgent"], + }, + ], +} +``` + + + +**Key points:** + +- `name` in bindings becomes the property on `env` (for example, `env.CounterAgent`) +- `class_name` must exactly match your exported class name +- `new_sqlite_classes` enables SQLite storage for state persistence +- `nodejs_compat` flag is required for the agents package + +## 4. Configure TypeScript and Vite + +If you use `@callable()` decorators (as in the example above), you need two build configurations. + +**tsconfig.json** — extend `agents/tsconfig` (or set `"target": "ES2021"` manually): + +```json +{ + "extends": "agents/tsconfig" +} +``` + +If you have an existing `tsconfig.json` with custom settings, you can extend and override: + +```json +{ + "extends": "agents/tsconfig", + "compilerOptions": { + "paths": { "~/*": ["./src/*"] } + } +} +``` + +**vite.config.ts** — add the `agents()` plugin (handles TC39 decorator transforms for Vite 8): + + + +```ts +import agents from "agents/vite"; + +export default defineConfig({ + plugins: [ + agents(), + // ... your existing plugins + ], +}); +``` + + + +If your project does not use Vite, the `tsconfig.json` change alone is sufficient — your bundler must support TC39 decorators (stage 3, version `2023-11`). + +For more details, refer to the [TypeScript configuration](/agents/platform/configuration/#typescript-configuration) and [Vite configuration](/agents/platform/configuration/#vite-configuration) reference. + +## 5. Export the Agent class + +Your agent class must be exported from your main entry point. Update your `src/index.ts`: + + + +```ts +// Export the agent class (required for Durable Objects) +export { CounterAgent } from "./agents/counter"; + +// Your existing exports... +export default { + // ... +} satisfies ExportedHandler; +``` + + + +## 6. Wire up routing + +Choose the approach that matches your project structure: + +### Plain Workers (fetch handler) + + + +```ts +import { routeAgentRequest } from "agents"; +export { CounterAgent } from "./agents/counter"; + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext) { + // Try agent routing first + const agentResponse = await routeAgentRequest(request, env); + if (agentResponse) return agentResponse; + + // Your existing routing logic + const url = new URL(request.url); + if (url.pathname === "/api/hello") { + return Response.json({ message: "Hello!" }); + } + + return new Response("Not found", { status: 404 }); + }, +} satisfies ExportedHandler; +``` + + + +### Hono + + + +```ts +import { Hono } from "hono"; +import { agentsMiddleware } from "hono-agents"; +export { CounterAgent } from "./agents/counter"; + +const app = new Hono<{ Bindings: Env }>(); + +// Add agents middleware - handles WebSocket upgrades and agent HTTP requests +app.use("*", agentsMiddleware()); + +// Your existing routes continue to work +app.get("/api/hello", (c) => c.json({ message: "Hello!" })); + +export default app; +``` + + + +### With static assets + +If you are serving static assets alongside agents, static assets are served first by default. Your Worker code only runs for paths that do not match a static asset: + + + +```ts +import { routeAgentRequest } from "agents"; +export { CounterAgent } from "./agents/counter"; + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext) { + // Static assets are served automatically before this runs + // This only handles non-asset requests + + // Route to agents + const agentResponse = await routeAgentRequest(request, env); + if (agentResponse) return agentResponse; + + return new Response("Not found", { status: 404 }); + }, +} satisfies ExportedHandler; +``` + + + +Configure assets in the Wrangler configuration file: + + + +```jsonc +{ + "assets": { + "directory": "./public", + }, +} +``` + + + +## 7. Generate TypeScript types + +Do not hand-write your `Env` interface. Run [`wrangler types`](/workers/wrangler/commands/general/#types) to generate a type definition file that matches your Wrangler configuration. This catches mismatches between your config and code at compile time instead of at deploy time. + +Re-run `wrangler types` whenever you add or rename a binding. + +```sh +npx wrangler types +``` + +This creates a type definition file with all your bindings typed, including your agent Durable Object namespaces. The `Agent` class defaults to using the generated `Env` type, so you do not need to pass it as a type parameter — `extends Agent` is sufficient unless you need to pass a second type parameter for state (for example, `Agent`). + +Refer to [Configuration](/agents/platform/configuration/#generating-types) for more details on type generation. + +## 8. Connect from the frontend + +### React + + + +```tsx +import { useState } from "react"; +import { useAgent } from "agents/react"; +import type { CounterAgent, CounterState } from "./agents/counter"; + +function CounterWidget() { + const [count, setCount] = useState(0); + + const agent = useAgent({ + agent: "CounterAgent", + onStateUpdate: (state) => setCount(state.count), + }); + + return ( + <> + {count} + + + + ); +} +``` + + + +Key points: + +- `useAgent` connects to your agent via WebSocket +- `onStateUpdate` fires whenever the agent's state changes +- `agent.stub.methodName()` calls methods marked with `@callable()` on your agent + +### Vanilla JavaScript + + + +```ts +import { AgentClient } from "agents/client"; + +const agent = new AgentClient({ + agent: "CounterAgent", + name: "user-123", // Optional: unique instance name + onStateUpdate: (state) => { + document.getElementById("count").textContent = state.count; + }, +}); + +// Call methods +document.getElementById("increment").onclick = () => agent.call("increment"); +``` + + + + + +## Deploy to Cloudflare + +```sh +npm run deploy +``` + +Your agent is now live on Cloudflare's global network, running close to your users. + + + + + + diff --git a/src/content/docs/agents/agent-class/communication-channels/chat/client-sdk.mdx b/src/content/docs/agents/agent-class/communication-channels/chat/client-sdk.mdx index 32340fabe03..915f3b7292e 100644 --- a/src/content/docs/agents/agent-class/communication-channels/chat/client-sdk.mdx +++ b/src/content/docs/agents/agent-class/communication-channels/chat/client-sdk.mdx @@ -673,7 +673,7 @@ const agentTools = useAgentToolEvents({ agent }); diff --git a/src/content/docs/agents/runtime/lifecycle/get-current-agent.mdx b/src/content/docs/agents/agent-class/get-current-agent.mdx similarity index 99% rename from src/content/docs/agents/runtime/lifecycle/get-current-agent.mdx rename to src/content/docs/agents/agent-class/get-current-agent.mdx index e33dd267c40..96a3a1402a5 100644 --- a/src/content/docs/agents/runtime/lifecycle/get-current-agent.mdx +++ b/src/content/docs/agents/agent-class/get-current-agent.mdx @@ -3,7 +3,7 @@ title: getCurrentAgent() description: Access the current Agent context from external utility functions using getCurrentAgent() in the Agents SDK. pcx_content_type: concept sidebar: - order: 29 + order: 17 products: - agents --- @@ -283,7 +283,7 @@ The context available depends on how the method was invoked: | `onEmail()` | Yes | No | No | Yes | | Custom method (via RPC) | Yes | Yes | No | No | | Scheduled task | Yes | No | No | No | -| Queue callback | Yes | Depends | Depends | Depends | +| Queue callback | Yes | No | No | No | ## Best practices diff --git a/src/content/docs/agents/agent-class/get-started.mdx b/src/content/docs/agents/agent-class/get-started.mdx new file mode 100644 index 00000000000..34278b59f3a --- /dev/null +++ b/src/content/docs/agents/agent-class/get-started.mdx @@ -0,0 +1,245 @@ +--- +title: Get started with the Agent class +pcx_content_type: get-started +sidebar: + order: 0 + label: Get started +description: Build a counter with the Agent class, with persistent state that syncs to a React frontend in real time. +products: + - agents +--- + +import { + TypeScriptExample, + WranglerConfig, + PackageManagers, + Render, +} from "~/components"; + + + +Build AI agents that persist, think, and act. Agents run on Cloudflare's global network, maintain state across requests, and connect to clients in real-time via WebSockets. + +**What you will build:** A counter agent with persistent state that syncs to a React frontend in real-time. + +**Time:** ~10 minutes + +## Create a new project + + + +Then install dependencies and start the dev server: + +```sh +cd agents-starter +npm install +npm run dev +``` + +This creates a project with: + +- `src/server.ts` — Your agent code +- `src/client.tsx` — React frontend +- `wrangler.jsonc` — Cloudflare configuration +- `tsconfig.json` — Extends `agents/tsconfig` for correct decorator and module settings +- `vite.config.ts` — Includes the `agents/vite` plugin for decorator support + +The starter template includes two important SDK integrations. If you are setting up a project manually, add both: + +**tsconfig.json** — extends `agents/tsconfig`, which sets `target: "ES2021"` and other recommended options: + +```json +{ + "extends": "agents/tsconfig" +} +``` + +**vite.config.ts** — includes the `agents()` plugin, which handles TC39 decorator transforms (required for `@callable()` in Vite 8): + +```ts +import { cloudflare } from "@cloudflare/vite-plugin"; +import react from "@vitejs/plugin-react"; +import agents from "agents/vite"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [agents(), react(), cloudflare()], +}); +``` + +Open [http://localhost:5173](http://localhost:5173) to see your agent in action. + +## Your first agent + +Build a simple counter agent from scratch. Replace `src/server.ts`: + + + +```ts title="src/server.ts" +import { Agent, routeAgentRequest, callable } from "agents"; + +// Define the state shape +export type CounterState = { + count: number; +}; + +// Create the agent +export class CounterAgent extends Agent { + // Initial state for new instances + initialState: CounterState = { count: 0 }; + + // Methods marked with @callable can be called from the client + @callable() + increment() { + this.setState({ count: this.state.count + 1 }); + return this.state.count; + } + + @callable() + decrement() { + this.setState({ count: this.state.count - 1 }); + return this.state.count; + } + + @callable() + reset() { + this.setState({ count: 0 }); + } +} + +// Route requests to agents +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext) { + return ( + (await routeAgentRequest(request, env)) ?? + new Response("Not found", { status: 404 }) + ); + }, +} satisfies ExportedHandler; +``` + + + +Update `wrangler.jsonc` to register the agent: + + + +```jsonc +{ + "name": "my-agent", + "main": "src/server.ts", + "upload_source_maps": true, + "compatibility_date": "$today", + "compatibility_flags": ["nodejs_compat"], + "durable_objects": { + "bindings": [ + { + "name": "CounterAgent", + "class_name": "CounterAgent", + }, + ], + }, + "migrations": [ + { + "tag": "v1", + "new_sqlite_classes": ["CounterAgent"], + }, + ], +} +``` + + + +**Key points:** + +- `name` in bindings becomes the property on `env` (for example, `env.CounterAgent`) +- `class_name` must exactly match your exported class name +- `new_sqlite_classes` enables SQLite storage for state persistence +- `nodejs_compat` flag is required for the agents package + +## Connect from React + +Replace `src/client.tsx`: + +```tsx title="src/client.tsx" +import "./styles.css"; +import { createRoot } from "react-dom/client"; +import { useState } from "react"; +import { useAgent } from "agents/react"; +import type { CounterAgent, CounterState } from "./server"; + +export default function App() { + const [count, setCount] = useState(0); + + // Connect to the Counter agent + const agent = useAgent({ + agent: "CounterAgent", + onStateUpdate: (state) => setCount(state.count), + }); + + return ( +
+

Counter Agent

+

{count}

+
+ + + +
+
+ ); +} + +const root = createRoot(document.getElementById("root")!); +root.render(); +``` + +Key points: + +- `useAgent` connects to your agent via WebSocket +- `onStateUpdate` fires whenever the agent's state changes +- `agent.stub.methodName()` calls methods marked with `@callable()` on your agent + + + +## Connect from vanilla JavaScript + +If you are not using React: + + + +```ts +import { AgentClient } from "agents/client"; + +const agent = new AgentClient({ + agent: "CounterAgent", + name: "my-counter", // optional, defaults to "default" + onStateUpdate: (state) => { + console.log("New count:", state.count); + }, +}); + +// Call methods +await agent.call("increment"); +await agent.call("reset"); +``` + + + +## Deploy to Cloudflare + +```sh +npm run deploy +``` + +Your agent is now live on Cloudflare's global network, running close to your users. + + + + + + diff --git a/src/content/docs/agents/agent-class/index.mdx b/src/content/docs/agents/agent-class/index.mdx index bf96338ed85..f19a485b5b9 100644 --- a/src/content/docs/agents/agent-class/index.mdx +++ b/src/content/docs/agents/agent-class/index.mdx @@ -1,22 +1,61 @@ --- title: Agent class -description: Reference for the Agent base class, its hooks, SQL storage, and error handling in the Agents SDK. +description: Reference for the legacy Agent base class, its hooks, SQL storage, and error handling in the Agents SDK. New agents should start with a harness on a Durable Object. pcx_content_type: reference sidebar: - order: 8 + order: 11 group: hideIndex: false + badge: Legacy products: - agents --- import { Render, LinkCard } from "~/components"; -This page provides an overview of the Agents SDK. For detailed documentation on each feature, refer to the linked reference pages. + -## Overview +The `Agent` class is a [Durable Object](/durable-objects/) with the [Lifecycle](/agents/lifecycle/) and a fixed set of [capabilities](/agents/capabilities/) already installed. Extend it when you need a feature that only the `Agent` class has today: -The Agents SDK provides two main APIs: +- `@callable()` methods and the `useAgent` client +- [Chat agents](/agents/agent-class/communication-channels/chat/) with `AIChatAgent` and `useAgentChat` +- [Voice](/agents/agent-class/communication-channels/voice/), [email](/agents/agent-class/communication-channels/email/), and the other [communication channels](/agents/agent-class/communication-channels/) +- [Project Think](/agents/harnesses/think/), which extends `Agent`. + +The pages in this section document the `Agent` class and the classes that extend it. + +## Move from the Agent class to a harness + +Each `Agent` API calls a capability underneath. To move an agent off the `Agent` class, extend `DurableObject`, install the Lifecycle, and use the capability from this table directly. For how request, alarm, and WebSocket handling work on the Lifecycle, refer to [Move an existing Durable Object onto the Lifecycle](/agents/getting-started/add-to-existing-project/#move-an-existing-durable-object-onto-the-lifecycle). + +| Agent API | Capability | +| ---------------------------------------------------------------------- | ------------------------------------------------ | +| `this.schedule()`, `this.scheduleEvery()`, `this.cancelSchedule()` | [`Scheduler`](/agents/capabilities/#scheduler) | +| `this.queue()`, `this.dequeue()`, `this.getQueue()` | [`Queue`](/agents/capabilities/#queue) | +| `this.addMcpServer()`, `this.removeMcpServer()`, `this.mcp` | [`MCPClientManager`](/agents/capabilities/#mcp-client) | +| `this.state`, `this.setState()`, `onStateChanged()` | [`State`](/agents/capabilities/#state) | +| `onConnect()`, `onMessage()`, `this.broadcast()`, `getConnections()` | [`WebSockets`](/agents/capabilities/#websockets) | +| `this.tasks` | [`Tasks`](/agents/capabilities/#tasks) | + +To stay on the `Agent` class and add another capability, such as the [Pi harness](/agents/harnesses/pi/), install it on `this.lifecycle` in the constructor: + +```ts +import { Agent } from "agents"; +import { PiHarness } from "agents/harness/pi"; + +export class Assistant extends Agent { + harness = new PiHarness({ + /* model and tools */ + }); + + constructor(ctx: DurableObjectState, env: Env) { + super(ctx, env); + this.lifecycle.use(this.harness); + } +} +``` + +The Agents SDK provides two main APIs for the Agent class: | API | Description | | ----------------------------- | -------------------------------------------------------------------------------- | @@ -25,7 +64,7 @@ The Agents SDK provides two main APIs: :::note -Agents require [Cloudflare Durable Objects](/durable-objects/). Refer to [Configuration](/agents/runtime/operations/configuration/) to learn how to add the required bindings. +Agents require [Cloudflare Durable Objects](/durable-objects/). Refer to [Configuration](/agents/platform/configuration/) to learn how to add the required bindings. ::: @@ -54,7 +93,7 @@ Each Agent can have millions of instances. Each instance is a separate micro-ser -## Lifecycle +## Hooks ```mermaid flowchart TD @@ -101,13 +140,13 @@ flowchart TD | **MCP Client** | `addMcpServer()`, `removeMcpServer()`, `getMcpServers()` | [MCP Client API](/agents/model-context-protocol/apis/client-api/) | | **AI Models** | Workers AI, OpenAI, Anthropic bindings | [Using AI models](/agents/agent-class/using-ai-models/) | | **Protocol messages** | `shouldSendProtocolMessages()`, `isConnectionProtocolEnabled()` | [Protocol messages](/agents/agent-class/protocol-messages/) | -| **Context** | `getCurrentAgent()` | [getCurrentAgent()](/agents/runtime/lifecycle/get-current-agent/) | -| **Tracing** | `wrapAISDK()` | [Tracing](/agents/runtime/operations/observability/tracing/) | -| **Diagnostics channels** | `subscribe()`, diagnostics channels | [Diagnostics channels](/agents/runtime/operations/observability/diagnostics-channels/) | +| **Context** | `getCurrentAgent()` | [getCurrentAgent()](/agents/lifecycle/get-current-agent/) | +| **Tracing** | `wrapAISDK()` | [Tracing](/agents/platform/observability/tracing/) | +| **Diagnostics channels** | `subscribe()`, diagnostics channels | [Diagnostics channels](/agents/platform/observability/diagnostics-channels/) | | **Sub-agents** | `subAgent()`, `abortSubAgent()`, `deleteSubAgent()` | [Sub-agents](/agents/agent-class/sub-agents/) | | **Agents as tools** | `runAgentTool()`, `clearAgentToolRuns()`, `hasAgentToolRun()` | [Agents as tools](/agents/agent-class/agent-tools/) | -| **Agent Skills** | `skills` registry, bundled skill sources, script runners | [Agent Skills](/agents/runtime/execution/agent-skills/) | -| **Sessions** | `Session.create()`, context blocks, compaction, search | [Sessions](/agents/runtime/lifecycle/sessions/) | +| **Agent Skills** | `skills` registry, bundled skill sources, script runners | [Agent Skills](/agents/tools/agent-skills/) | +| **Sessions** | `Session.create()`, context blocks, compaction, search | [Sessions](/agents/capabilities/sessions/) | | **Think** | `Think` base class, workspace tools, lifecycle hooks, extensions | [Think](/agents/harnesses/think/) | | **Chat SDK** | `createChatSdkState()`, `ChatSdkStateAgent` | [Chat SDK](/agents/agent-class/communication-channels/chat-sdk/) | @@ -211,14 +250,14 @@ Refer to [Routing](/agents/agent-class/routing/) for custom paths, CORS, and ins ## Next steps diff --git a/src/content/docs/agents/agent-class/internals.mdx b/src/content/docs/agents/agent-class/internals.mdx index 27bf5913a3a..4bce519f0be 100644 --- a/src/content/docs/agents/agent-class/internals.mdx +++ b/src/content/docs/agents/agent-class/internals.mdx @@ -1,6 +1,6 @@ --- title: Agent class internals -description: Explore how the Agent class extends Durable Objects to provide state, WebSockets, scheduling, and RPC. +description: Explore how the Agent class composes a Durable Object lifecycle and capabilities for state, WebSockets, scheduling, and RPC. pcx_content_type: concept sidebar: order: 1 @@ -10,13 +10,20 @@ products: The core of the `agents` library is the `Agent` class. You extend it, override a few methods, and get state management, WebSockets, scheduling, RPC, and more for free. This page explains how `Agent` is built, layer by layer, so you understand what is happening under the hood. -The snippets shown here are illustrative and do not necessarily represent best practices. For the full API, refer to the [API reference](/agents/runtime/) and the [source code](https://github.com/cloudflare/agents/blob/main/packages/agents/src/index.ts). +The snippets shown here are illustrative and do not necessarily represent best practices. For the full API, refer to the [API reference](/agents/agent-class/) and the [source code](https://github.com/cloudflare/agents/blob/main/packages/agents/src/index.ts). ## What is the Agent? -The `Agent` class is an extension of `DurableObject` — agents _are_ Durable Objects. If you are not familiar with Durable Objects, read [What are Durable Objects](/durable-objects/) first. At their core, Durable Objects are globally addressable (each instance has a unique ID), single-threaded compute instances with long-term storage (key-value and SQLite). +`Agent` directly extends the platform `DurableObject` class. Each Agent is a globally addressable compute instance with durable key-value and SQLite storage. For more information, refer to [What are Durable Objects](/durable-objects/). -`Agent` does not extend `DurableObject` directly. It extends `Server` from the [`partyserver`](https://github.com/cloudflare/partykit/tree/main/packages/partyserver) package, which extends `DurableObject`. Think of it as layers: **DurableObject** > **Server** > **Agent**. +Each Agent also installs a [`Lifecycle`](/agents/lifecycle/) instance. The lifecycle connects the Agent to reusable [capabilities](/agents/capabilities/). + +```txt +DurableObject +└── Agent + └── Lifecycle + └── capabilities +``` ## Layer 0: Durable Object @@ -81,9 +88,9 @@ export class MyDurableObject extends DurableObject { ### `alarm()` -HTTP and RPC requests are not the only entrypoints for a Durable Object. Alarms allow developers to schedule an event to trigger at a later time. Whenever the next alarm is due, the runtime will call the `alarm()` method, which is left to the developer to implement. +HTTP and RPC requests are not the only Durable Object entry points. A plain Durable Object can schedule one alarm with `ctx.storage.setAlarm()` and handle it through `alarm()`. For more information, refer to [Alarms](/durable-objects/api/alarms/). -To schedule an alarm, you can use the `this.ctx.storage.setAlarm()` method. For more information, refer to [Alarms](/durable-objects/api/alarms/). +Lifecycle owns the physical alarm for an Agent. Do not override `alarm()` or call `ctx.storage.setAlarm()` from Agent code. Use `this.schedule()` for Agent callbacks. A reusable capability can push durable jobs through its scoped Lifecycle job queue. ### `this.ctx` @@ -107,94 +114,25 @@ const token = this.ctx.storage.get("someToken"); Lastly, it is worth mentioning that the Durable Object also has the Worker `Env` in `this.env`. Learn more in [Bindings](/workers/runtime-apis/bindings). -## Layer 1: `Server` (partyserver) - -Now that you have seen what Durable Objects provide out of the box, the `Server` class from [`partyserver`](https://github.com/cloudflare/partykit/tree/main/packages/partyserver) will make more sense. It is an opinionated `DurableObject` wrapper that replaces low-level primitives with developer-friendly callbacks. - -`Server` does not add any storage operations of its own — it only wraps the Durable Object lifecycle. - -### Addressing - -`partyserver` exposes helpers to address Durable Objects by name instead of going through bindings manually. This includes a URL routing scheme (`/servers/:durableClass/:durableName`) that the Agent layer builds on. - -```ts -// Note the await here! -const stub = await getServerByName(env.MY_DO, "foo"); - -// We can still call RPC methods. -await stub.bar(); -``` - -The URL scheme also enables a request router. In the Agent layer, this is re-exported as `routeAgentRequest`: - -```ts - async fetch(request: Request, env: Env, ctx: ExecutionContext) { - const res = await routeAgentRequest(request, env); - - if (res) return res; - - return new Response("Not found", { status: 404 }); - } -``` +## Layer 1: lifecycle composition -### `onStart` +`Agent` uses `Lifecycle.install(this)` to install its platform-facing request, alarm, and hibernating WebSocket event handlers. Agent subclasses implement semantic callbacks such as `onStart()` and `onRequest()` instead of forwarding those platform events. -The addressing layer allows `Server` to expose an `onStart` callback that runs every time the Durable Object starts up (after eviction, hibernation, or first creation) and before any `fetch` or RPC call. +The `Agent` class installs `State`, `Queue`, `Scheduler`, `Tasks`, `MCPClientManager`, and `WebSockets`, plus its internal dynamic-agent capability. Capabilities start before Agent startup. Selective capabilities run before catch-alls, such as `WebSockets`. Queues, schedules, and tasks share one Lifecycle-managed Durable Object alarm. -```ts -class MyServer extends Server { - onStart() { - // Some initialization logic that you wish - // to run every time the DO is started up. - const sql = this.ctx.storage.sql; - sql.exec(`...`); - } -} -``` +You can install additional capabilities through `this.lifecycle.use(capability)`. The same capability can also run on a class that directly extends `DurableObject`. -### `onRequest` and `onConnect` - -`Server` already implements `fetch` for the underlying Durable Object and exposes two different callbacks that developers can make use of, `onRequest` and `onConnect` for HTTP requests and incoming WS connections, respectively (WebSocket connections are accepted by default). - -```ts -class MyServer extends Server { - async onRequest(request: Request) { - const url = new URL(request.url); - - return new Response(`Hello from ${url.origin}!`); - } - - async onConnect(conn, ctx) { - const { request } = ctx; - const url = new URL(request.url); - - // Connections are a WebSocket wrapper - conn.send(`Hello from ${url.origin}!`); - } -} -``` - -### WebSockets - -Just as `onConnect` is the callback for every new connection, `Server` also provides wrappers on top of the default callbacks from the `DurableObject` class: `onMessage`, `onClose` and `onError`. - -There's also `this.broadcast` that sends a WS message to all connected clients (no magic, just a loop over `this.getConnections()`!). - -### `this.name` - -It is hard to get a Durable Object's `name` from within it. `partyserver` tries to make it available in `this.name` but it is not a perfect solution. Learn more about it in [this GitHub issue](https://github.com/cloudflare/workerd/issues/2240). +Lifecycle reads names assigned through `idFromName()` or `getByName()` from `ctx.id.name`. The Agent exposes this value as `this.name`. ## Layer 2: Agent -Now finally, the `Agent` class. `Agent` extends `Server` and provides opinionated primitives for stateful, schedulable, and observable agents that can communicate via RPC, WebSockets, and (even!) email. +The `Agent` class directly extends `DurableObject`. It adds state, scheduling, RPC, WebSockets, MCP clients, email, and other agent-focused APIs. ### `this.state` and `this.setState()` -One of the core features of `Agent` is **automatic state persistence**. Developers define the shape of their state via the generic parameter and `initialState` (which is only used if no state exists in storage), and the Agent handles loading, saving, and broadcasting state changes (check `Server`'s `this.broadcast()` above). +Developers define the state shape through a generic parameter and `initialState`. The `State` capability owns lazy loading, caching, validation, and persistence. The `WebSockets` capability owns state frames, broadcasting, and connection flags. -`this.state` is a getter that lazily loads state from storage (SQL). State is persisted across Durable Object evictions when it is updated with `this.setState()`, which automatically serializes the state and writes it back to storage. - -There's also `this.onStateChanged` that you can override to react to state changes. +`this.state` reads the stored value, while `this.setState()` validates and persists changes. `initialState` seeds a value only when none exists. Override `this.onStateChanged` to react after a change. ```ts class MyAgent extends Agent { @@ -210,7 +148,7 @@ class MyAgent extends Agent { } ``` -State is stored in the `cf_agents_state` SQL table. State messages are sent with `type: "cf_agent_state"` (both from the client and the server). Since `agents` provides [JS and React clients](/agents/agent-class/state/#synchronizing-state), real-time state updates are available out of the box. +State is stored in the `cf_agents_state` SQL table. State messages are sent with `type: "cf_agent_state"` (both from the client and the server). Since `agents` provides [JS and React clients](/agents/agent-class/state/#client-side-state-sync), real-time state updates are available out of the box. ### `this.sql` @@ -251,7 +189,9 @@ class MyAgent extends Agent { } ``` -Clients can invoke this method by sending a WebSocket message: +The default `"cf-websocket"` transport invokes decorated methods through JSON RPC frames. Decorators do not populate the native Cap'n Web interface. Plain Durable Objects expose their interface through `WebSockets({ callables: new MyCalls() })`, where `MyCalls` extends `RpcTarget`. + +Clients can invoke the decorated method by sending a WebSocket message: ```json { @@ -272,7 +212,9 @@ console.log(result); // 5 ### `this.queue` and friends -Agents include a built-in task queue for deferred execution. This is useful for offloading work or retrying operations. The available methods are `this.queue`, `this.dequeue`, `this.dequeueAll`, `this.dequeueAllByCallback`, `this.getQueue`, and `this.getQueues`. +Agent queue methods delegate to the `Queue` capability. `this.queue()` durably accepts a callback and returns its item identifier. Queue callbacks run from the alarm loop, one at a time in push order. + +The methods `this.dequeue`, `this.dequeueAll`, `this.dequeueAllByCallback`, `this.getQueue`, and `this.getQueues` return promises. Await inspection and removal operations. Queue items expose `createdAt` as a Unix timestamp in seconds, replacing `created_at`. ```ts class MyAgent extends Agent { @@ -287,7 +229,9 @@ class MyAgent extends Agent { } ``` -Tasks are stored in the `cf_agents_queues` SQL table and are automatically flushed in sequence. If a task succeeds, it is automatically dequeued. +Each queued item is a job in `cf_agents_jobs`. Startup migrates the legacy `cf_agents_queues` rows and drops that table. Callbacks no longer inherit the enqueuing request or connection through `getCurrentAgent()`; only the host Agent remains in scope. + +A callback can run again after interruption, so external effects must be idempotent. An item that exhausts its application retries is removed after error reporting. ### `this.schedule` and friends @@ -324,7 +268,7 @@ class MyAgent extends Agent { } ``` -Schedules are stored in the `cf_agents_schedules` SQL table. Cron schedules automatically reschedule themselves after execution, while one-time schedules are deleted. +The `Scheduler` stores schedules in the Lifecycle-owned `cf_agents_jobs` queue. Startup migrates legacy `cf_agents_schedules` rows and drops that table. Cron schedules reschedule after execution, while one-time schedules complete and are deleted. ### `this.mcp` and friends @@ -402,7 +346,7 @@ function someUtilityFunction() { ### `this.onError` -`Agent` extends `Server`'s `onError` so it can be used to handle errors that are not necessarily WebSocket errors. It is called with a `Connection` or `unknown` error. +`Agent` exposes `onError()` for WebSocket and other Agent errors. It receives either a `Connection` with an error, or an error value. ```ts class MyAgent extends Agent { @@ -411,12 +355,12 @@ class MyAgent extends Agent { // WebSocket connection error console.error("Connection error:", error); } else { - // Server error - console.error("Server error:", connectionOrError); + // Non-WebSocket error + console.error("Agent error:", connectionOrError); } // Optionally throw to propagate the error - throw connectionOrError; + throw error ?? connectionOrError; } } ``` @@ -467,20 +411,22 @@ Configure agent behavior by overriding `static options` on your class. All field ```ts export class MyAgent extends Agent { static options = { - hibernate: true, sendIdentityOnConnect: false, retry: { maxAttempts: 5, baseDelayMs: 200, maxDelayMs: 5000 }, + maxAlarmMemoryLimitStrikes: 3, }; } ``` -| Option | Type | Default | Description | -| --- | --- | --- | --- | -| `hibernate` | `boolean` | `true` | Whether the agent hibernates when inactive. WebSocket connections stay open while the DO sleeps | -| `sendIdentityOnConnect` | `boolean` | `true` | Send identity (agent name, instance name) to clients on WebSocket connect. Set to `false` to hide sensitive instance names | -| `hungScheduleTimeoutSeconds` | `number` | `30` | Timeout before a running interval schedule is considered hung and force-reset. Increase for long-running callbacks | -| `keepAliveIntervalMs` | `number` | `30000` | Interval in milliseconds for `keepAlive()` alarm heartbeats. Lower values mean faster recovery but more frequent alarms | -| `retry` | `RetryOptions` | `{ maxAttempts: 3, baseDelayMs: 100, maxDelayMs: 3000 }` | Default retry options for `schedule()`, `queue()`, and `this.retry()`. Per-task options override these defaults | +The default `"cf-websocket"` transport uses the WebSocket Hibernation API. A client can select `transport: "capnweb"` instead, which keeps the object in memory while the connection is open. There is no server-side `hibernate` option. + +| Option | Type | Default | Description | +| ---------------------------- | -------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| `sendIdentityOnConnect` | `boolean` | `true` | Send identity (agent name, instance name) to clients on WebSocket connect. Set to `false` to hide sensitive instance names | +| `hungScheduleTimeoutSeconds` | `number` | `30` | Timeout before a running interval schedule is considered hung and force-reset. Increase for long-running callbacks | +| `keepAliveIntervalMs` | `number` | `30000` | Interval in milliseconds for `keepAlive()` alarm heartbeats. Lower values mean faster recovery but more frequent alarms | +| `maxAlarmMemoryLimitStrikes` | `number` | `3` | Consecutive alarm memory-limit resets allowed before Lifecycle seals recovery-loop work | +| `retry` | `RetryOptions` | `{ maxAttempts: 3, baseDelayMs: 100, maxDelayMs: 3000 }` | Default retry options for `schedule()`, `queue()`, and `this.retry()`. Per-task options override these defaults | ### `this.keepAlive()` and `this.keepAliveWhile()` @@ -511,7 +457,7 @@ class MyAgent extends Agent { ### Routing -The `Agent` class re-exports the [addressing helpers](#addressing) as `getAgentByName` and `routeAgentRequest`. +The Agents SDK exports `getAgentByName` and `routeAgentRequest` for named Agent routing. ```ts const stub = await getAgentByName(env.MY_DO, "foo"); @@ -527,6 +473,6 @@ return new Response("Not found", { status: 404 }); The [`AIChatAgent`](/agents/agent-class/communication-channels/chat/chat-agents/) class from `@cloudflare/ai-chat` extends `Agent` with an opinionated layer for AI chat. It adds automatic message persistence to SQLite, resumable streaming, tool support (server-side, client-side, and human-in-the-loop), and a React hook (`useAgentChat`) for building chat UIs. -The full hierarchy is: **DurableObject** > **Server** > **Agent** > **AIChatAgent**. +The full class hierarchy is **DurableObject** > **Agent** > **AIChatAgent**. `Agent` composes Lifecycle and its capabilities instead of adding another base class. If you are building a chat agent, start with `AIChatAgent`. If you need lower-level control or are not building a chat interface, use `Agent` directly. diff --git a/src/content/docs/agents/agent-class/routing.mdx b/src/content/docs/agents/agent-class/routing.mdx index 231edc4fa6a..2a27d860f16 100644 --- a/src/content/docs/agents/agent-class/routing.mdx +++ b/src/content/docs/agents/agent-class/routing.mdx @@ -539,7 +539,7 @@ const response = await routeAgentRequest(request, env, {
-These hooks are useful for authentication and validation. Refer to [Cross-domain authentication](/agents/runtime/operations/cross-domain-authentication/) for detailed examples. +These hooks are useful for authentication and validation. Refer to [Cross-domain authentication](/agents/platform/cross-domain-authentication/) for detailed examples. ## Server-side agent access @@ -792,7 +792,7 @@ export default app; -For WebSocket authentication patterns (tokens in URLs, JWT refresh), refer to [Cross-domain authentication](/agents/runtime/operations/cross-domain-authentication/). +For WebSocket authentication patterns (tokens in URLs, JWT refresh), refer to [Cross-domain authentication](/agents/platform/cross-domain-authentication/). ## Troubleshooting @@ -909,7 +909,7 @@ class SecureAgent extends Agent { @@ -921,6 +921,6 @@ class SecureAgent extends Agent { diff --git a/src/content/docs/agents/agent-class/websockets.mdx b/src/content/docs/agents/agent-class/websockets.mdx index 8a71e008561..ac51d4a34ef 100644 --- a/src/content/docs/agents/agent-class/websockets.mdx +++ b/src/content/docs/agents/agent-class/websockets.mdx @@ -572,6 +572,6 @@ Refer to [Client SDK](/agents/agent-class/communication-channels/chat/client-sdk diff --git a/src/content/docs/agents/capabilities/index.mdx b/src/content/docs/agents/capabilities/index.mdx new file mode 100644 index 00000000000..3114a7a8e44 --- /dev/null +++ b/src/content/docs/agents/capabilities/index.mdx @@ -0,0 +1,344 @@ +--- +title: Capabilities +description: Add a harness, MCP client, scheduling, queues, WebSockets, state, sessions, streams, and durable tasks to a Durable Object with Lifecycle capabilities. +pcx_content_type: concept +sidebar: + order: 5 + group: + hideIndex: false +products: + - agents +--- + +import { TypeScriptExample } from "~/components"; + +A capability is a reusable piece of a Durable Object. Each one owns its tables and behavior, exposes a focused API, and implements only the [Lifecycle](/agents/lifecycle/) hooks it needs. You build an agent by installing the capabilities you want on one Durable Object: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { MCPClientManager } from "agents/mcp/client"; +import { Scheduler } from "agents/schedules"; +import { WebSockets } from "agents/websockets"; + +export class MyAgent extends DurableObject { + readonly mcp = new MCPClientManager("my-agent", "1.0.0", { env: this.env }); + readonly scheduler = new Scheduler({ callbacks: {} }); + readonly webSockets = new WebSockets(); + + readonly lifecycle = Lifecycle.install(this) + .use(this.mcp) + .use(this.scheduler) + .use(this.webSockets); +} +``` + + + +Capabilities share the object's storage and its alarm, but they do not depend on each other. Install each one once. Bindings and callbacks a capability needs go in its constructor. + +## Available capabilities + +The following capabilities ship with the `agents` package. Capabilities marked Experimental may change between releases. + +| Capability | Import | Status | What it does | +| ------------------------------------- | ------------------- | ------------ | -------------------------------------------------------------------- | +| [`PiHarness`](/agents/harnesses/pi/) | `agents/harness/pi` | Beta | Runs the Pi agent loop, with recovery after eviction | +| [`MCPClientManager`](#mcp-client) | `agents/mcp/client` | Stable | Connects to MCP servers and keeps the connections across restarts | +| [`Scheduler`](#scheduler) | `agents/schedules` | Stable | Runs callbacks after a delay, at a date, on a cron, or on an interval | +| [`WebSockets`](#websockets) | `agents/websockets` | Stable | Accepts hibernating connections that `useAgent` can reach | +| [`State`](#state) | `agents/state` | Stable | Stores one validated value, and syncs it to clients with `WebSockets` | +| [`Queue`](#queue) | `agents/queue` | Experimental | Runs background callbacks one at a time, in push order | +| [`RoutedAgents`](#routed-agents) | `agents/routing` | Experimental | Keeps a catalog of independent agents and forwards requests to them | +| [`Browser`](#browser) | `agents/browser` | Experimental | Keeps one persistent browser session open for the agent | +| [`Sessions`](#sessions) | `agents/sessions` | Experimental | Stores conversation history as a tree of messages | +| [`Streams`](#streams) | `agents/streams` | Experimental | Stores incremental output that clients can replay and follow | +| [`Tasks`](#tasks) | `agents/tasks` | Experimental | Runs replayable background work with journaled steps | + +`PiHarness`, `MCPClientManager`, `RoutedAgents`, and `Browser` give an agent its behavior. `Scheduler`, `Queue`, `WebSockets`, and `State` cover background work and connections. `Sessions`, `Streams`, and `Tasks` are lower-level pieces for [building your own harness](#build-your-own-harness). + +To write a capability of your own, refer to [Build a capability](/agents/lifecycle/build-a-capability/). + +## MCP client + +`MCPClientManager` connects your agent to [Model Context Protocol (MCP)](/agents/model-context-protocol/) servers. It stores the servers you add, reconnects to them on startup, and handles OAuth callbacks before your `onRequest()` runs: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { MCPClientManager } from "agents/mcp/client"; + +export class ToolAgent extends DurableObject { + readonly mcp = new MCPClientManager("tool-agent", "1.0.0", { + env: this.env, + }); + readonly lifecycle = Lifecycle.install(this).use(this.mcp); +} +``` + + + +Pass `env` when you connect to MCP servers over Durable Object RPC. Use `listTools()` for the raw tool list or `getAITools()` for AI SDK tools. For more information, refer to the [MCP client API](/agents/model-context-protocol/apis/client-api/). + +## Scheduler + +`Scheduler` runs registered callbacks after a delay, at a date, from a cron expression, or at a fixed interval: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { Scheduler } from "agents/schedules"; + +export class ReminderAgent extends DurableObject { + readonly scheduler = new Scheduler({ + callbacks: { + sendReminder: (payload: { message: string }) => { + console.log(payload.message); + }, + }, + }); + readonly lifecycle = Lifecycle.install(this).use(this.scheduler); + + async onRequest(): Promise { + const schedule = await this.scheduler.set(60, "sendReminder", { + message: "One minute has passed", + }); + return Response.json(schedule, { status: 201 }); + } +} +``` + + + +Register callbacks in the constructor so that every wake rebuilds the same map. `set()` and `every()` check the callback name and payload type against it. Cron and interval schedules deduplicate, so you can create them in `onStart()`. + +## WebSockets + +`WebSockets` accepts WebSocket upgrades and speaks the Agents protocol, so a plain Durable Object works with `useAgent` and `AgentClient`: + + + +```ts +import { DurableObject, RpcTarget } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { WebSockets } from "agents/websockets"; + +class RoomMethods extends RpcTarget { + ping(): string { + return "pong"; + } +} + +export class RoomAgent extends DurableObject { + readonly webSockets = new WebSockets({ + callables: new RoomMethods(), + handlers: { + onMessage: (connection, message) => connection.send(message), + }, + }); + readonly lifecycle = Lifecycle.install(this).use(this.webSockets); +} +``` + + + +Clients call the methods on `callables` with `agent.stub.ping()` or `agent.call("ping")`. Connections hibernate by default, so idle clients stay connected while the object leaves memory. + +## State + +`State` stores one validated value in the object's storage. Pass it to `WebSockets` to sync it with `useAgent().state`: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { State } from "agents/state"; +import { WebSockets } from "agents/websockets"; + +export class CounterAgent extends DurableObject { + readonly state: State<{ count: number }> = new State({ + initialState: { count: 0 }, + onChanged: (_state, source) => this.webSockets.broadcastState(source), + }); + readonly webSockets: WebSockets = new WebSockets({ state: this.state }); + readonly lifecycle = Lifecycle.install(this) + .use(this.state) + .use(this.webSockets); +} +``` + + + +`state.get()` reads the value and `state.set(next)` validates and stores it. Pass `validateStateChange` to reject a change by throwing. + +## Queue + +`Queue` runs background callbacks one at a time, in push order, from the object's alarm: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { Queue } from "agents/queue"; + +export class UploadAgent extends DurableObject { + readonly queue = new Queue({ + callbacks: { + processUpload: (payload: { fileName: string }) => { + console.log(`Processing ${payload.fileName}`); + }, + }, + retry: { maxAttempts: 3 }, + }); + readonly lifecycle = Lifecycle.install(this).use(this.queue); + + async onRequest(): Promise { + const item = await this.queue.push("processUpload", { + fileName: "report.pdf", + }); + return Response.json(item, { status: 202 }); + } +} +``` + + + +`push()` returns once the item is stored, without waiting for the callback. An interrupted callback can run again, so make external effects idempotent. + +## Routed agents + +`RoutedAgents` lets one hub Durable Object own many independent agents, such as one agent per chat. It stores each agent's ID and metadata, and forwards requests under `/{route}/{id}` to the right agent: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { RoutedAgents } from "agents/routing"; +import type { ChatAgent } from "./chat-agent"; + +export class UserHub extends DurableObject { + readonly chats = new RoutedAgents({ + namespace: this.env.ChatAgent, + route: "chats", + }); + readonly lifecycle = Lifecycle.install(this).use(this.chats); +} +``` + + + +The hub can be a plain Durable Object. Each routed agent must extend the [`Agent` class](/agents/agent-class/), because deletion uses its teardown. Manage the catalog with `create()`, `list()`, `get()`, `setMetadata()`, and `delete()`. + +## Browser + +`Browser` keeps one [Browser Run](/browser-run/) session open across requests, so tabs, cookies, and logins carry over: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Browser, browserRun } from "agents/browser"; +import { Lifecycle } from "agents/lifecycle"; + +export class ResearchAgent extends DurableObject { + readonly browser = new Browser({ provider: browserRun(this.env.BROWSER) }); + readonly lifecycle = Lifecycle.install(this).use(this.browser); +} +``` + + + +Give the browser to a model with `browserTool()`. For more information, refer to [Browser](/agents/tools/browser/). + +## Build your own harness + +A harness is the loop around the model. [`PiHarness`](/agents/harnesses/pi/) is one you can install today. To write your own, combine these three capabilities: + +- `Sessions` stores the conversation. +- `Streams` stores the model's output as it arrives. +- `Tasks` runs each turn as replayable background work. + +### Sessions + +`Sessions` stores conversation messages as a tree, with branches, compaction, full-text search, and attachments: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { Sessions } from "agents/sessions"; + +export class ChatAgent extends DurableObject { + readonly sessions = new Sessions(); + readonly lifecycle = Lifecycle.install(this).use(this.sessions); + readonly session = this.sessions.session(); +} +``` + + + +One `Sessions` capability holds many sessions by ID. Call `session()` with no ID when the object represents a single conversation. + +### Streams + +`Streams` stores incremental output as numbered chunks. A client can reconnect, replay what it missed, and follow new chunks: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { Streams } from "agents/streams"; + +export class OutputAgent extends DurableObject { + readonly streams = new Streams(); + readonly lifecycle = Lifecycle.install(this).use(this.streams); +} +``` + + + +Producers call `open()`, append JSON chunks, and finish with `close()` or `error()`. `sseResponse()` serves a stream as resumable Server-Sent Events (SSE). + +### Tasks + +`Tasks` runs background work as named steps. After an interruption, completed steps return their stored results and the run continues from the first unfinished step: + + + +```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: { + "publish-report@v1": async (input: { topic: string }, step: TaskStep) => { + const draft = await step.do("draft", () => `Draft about ${input.topic}`); + await step.sleep("review", "10 minutes"); + return { draft }; + }, + }, + }); + readonly lifecycle = Lifecycle.install(this).use(this.tasks); +} +``` + + + +`tasks.run(name, input)` returns a durable receipt. A durable sleep parks the run without keeping the object in memory. + +## Capabilities on the Agent class + +The [`Agent` class](/agents/agent-class/) installs `Scheduler`, `Queue`, `MCPClientManager`, `State`, `WebSockets`, and `Tasks` for you. Its methods, such as `this.schedule()`, `this.queue()`, `this.addMcpServer()`, and `this.setState()`, call those capabilities. Install any other capability on an Agent with `this.lifecycle.use()` in its constructor. diff --git a/src/content/docs/agents/runtime/lifecycle/sessions.mdx b/src/content/docs/agents/capabilities/sessions.mdx similarity index 100% rename from src/content/docs/agents/runtime/lifecycle/sessions.mdx rename to src/content/docs/agents/capabilities/sessions.mdx diff --git a/src/content/docs/agents/concepts/conversation-state-and-memory.mdx b/src/content/docs/agents/concepts/conversation-state-and-memory.mdx index 7c4c0220643..1925dd23835 100644 --- a/src/content/docs/agents/concepts/conversation-state-and-memory.mdx +++ b/src/content/docs/agents/concepts/conversation-state-and-memory.mdx @@ -1,7 +1,7 @@ --- title: Conversation state and memory pcx_content_type: concept -description: How agents store and recall information, including read-only context, writable short-form memory, searchable knowledge, and on-demand skills. +description: How agents store and recall information, including conversation history in Sessions, context blocks in the system prompt, frozen prompts for caching, and compaction. tags: - AI sidebar: @@ -10,563 +10,135 @@ products: - agents --- -import { TypeScriptExample, LinkCard, WranglerConfig } from "~/components"; +import { LinkCard, TypeScriptExample } from "~/components"; -Agents need memory to be useful over time. Without it, every conversation starts from zero. The agent forgets who the user is, what it learned, and what it was doing. Memory is what turns a stateless LLM call into a persistent, context-aware agent. +Agents need memory to be useful over time. Without it, every conversation starts from zero. The agent forgets who the user is, what it learned, and what it was doing. -The [Session API](/agents/runtime/lifecycle/sessions/) provides the memory layer for agents built on the Cloudflare Agents SDK. It manages two kinds of memory: **conversation history** (the messages and tool calls that make up a session) and **context memory** (persistent blocks injected into the system prompt that the agent can read, write, search, and load). +An agent on Cloudflare has two kinds of memory, and both live in its Durable Object's SQLite database: -Use this page when you need more than simple synced state or flat chat history. For small UI state, use [Store and sync state](/agents/agent-class/state/). For basic chat persistence, `AIChatAgent` stores messages for you. For opinionated long-term memory, Think builds on Session and context blocks. +- **Conversation history** is the messages and tool calls that make up a conversation. [Harnesses](/agents/harnesses/) store it for you. To store it yourself, use the [`Sessions`](/agents/capabilities/#sessions) capability from `agents/sessions`. +- **Context** is what the model reads in its system prompt on every turn: its identity, facts it learned, and knowledge it can search. Assemble it with `ContextBlocks` from `agents/context`. -:::caution[Experimental] - -The Session memory APIs currently use `agents/experimental/memory/session`. The concepts are stable, but import paths and details may change before graduation. +The two are separate on purpose, so an agent can have a prompt without a transcript, or a transcript without a prompt. +:::caution[Experimental] +`agents/sessions` and `agents/context` are experimental. Their APIs may change between releases. ::: -## Conversation history - -The most fundamental type of memory is the conversation itself: the messages between the user and the agent, the tool calls the agent made, and the results it received. The Session stores all of this in a tree-structured message history backed by a Session Provider, defaulting to SQLite. - - - -```ts -import { Session } from "agents/experimental/memory/session"; - -// Append messages as the conversation progresses -await session.appendMessage({ - id: `user-${crypto.randomUUID()}`, - role: "user", - parts: [{ type: "text", text: "What's the status of the deployment?" }], -}); - -// Read the full conversation history -const history = await session.getHistory(); -``` - - - -Conversation history persists across Durable Object hibernation and eviction. When the agent wakes up, the full history is available in SQLite. It does not need to be replayed or reconstructed. - -Messages are stored in a tree structure via `parent_id`, which enables branching conversations. When you `appendMessage` with a `parentId` that already has children, you create a branch, useful for features like response regeneration. `getHistory(leafId)` walks any chosen path through the tree. - -The Session also provides full-text search across the conversation history: - - - -```ts -const results = await session.search("deployment Friday", { limit: 10 }); -``` - - - -As conversations grow long, [compaction](#compaction) summarizes older messages to keep the context window manageable without losing the underlying data. - -## Context memory - -Context memory is persistent information injected into the system prompt, separate from the conversation history. It gives the agent access to identity, instructions, learned facts, knowledge bases, and reference material across every turn. - -The Session API supports four types of context memory, each suited to different kinds of information. The type is determined by the **provider** backing the context block. The Session detects the provider's capabilities automatically. - -### Read-only context - -This is your traditional system prompt: the agent's identity, personality, and instructions. You might write it directly in your codebase, load it from a `SOUL.md` file in R2, or fetch it from an API. The content is injected into the system prompt and the agent cannot modify it. - -A coding assistant might have a soul that defines its personality and constraints: - - - -```ts -import { Session } from "agents/experimental/memory/session"; - -const session = Session.create(this).withContext("soul", { - provider: { - get: async () => - "You are a senior TypeScript engineer. You write concise, " + - "well-tested code. You prefer composition over inheritance. " + - "When you are unsure, you say so rather than guessing.", - }, -}); -``` - - - -Or load it from R2 so you can update the agent's personality without redeploying: - - - -```ts -const session = Session.create(this).withContext("soul", { - provider: { - get: async () => { - const obj = await env.CONFIG_BUCKET.get("soul.md"); - return obj ? obj.text() : "You are a helpful assistant."; - }, - }, -}); -``` - - - -Read-only blocks are defined by providing an object with only a `get()` method. No tools are generated. The content appears in the system prompt and the agent has no way to change it. +For small UI state that syncs to clients, use the [`State`](/agents/capabilities/#state) capability instead. -### Writable short-form context - -Think of this as a scratchpad the agent maintains for itself, a place to jot down things it needs to remember. Like how Claude Code keeps a todo list of tasks to work through, or how a customer support agent might track what it has learned about the user during the conversation. - - - -```ts -const session = Session.create(this) - .withContext("memory", { - description: "Important facts learned during conversation", - maxTokens: 1100, - }) - .withContext("todos", { - description: "Task list, track what needs to be done and what is complete", - maxTokens: 2000, - }); -``` - - - -When you omit the `provider` option in the builder, the Session auto-wires to a SQLite-backed writable provider. The agent gets a `set_context` tool that lets it replace or append content to these blocks. Token limits are enforced, so the agent cannot write more than the `maxTokens` budget allows. - -The system prompt renders writable blocks with a token usage indicator so the agent knows how much space it has left: - -```txt -══════════════════════════════════════════════ -MEMORY (Important facts learned during conversation) [45% — 495/1100 tokens] [writable] -══════════════════════════════════════════════ -User prefers dark mode. -User's project uses React and TypeScript. -Deployment target is Cloudflare Workers. - -══════════════════════════════════════════════ -TODOS (Task list) [12% — 240/2000 tokens] [writable] -══════════════════════════════════════════════ -- [x] Set up project scaffolding -- [ ] Add authentication middleware -- [ ] Write integration tests -``` - -The content persists across messages and survives hibernation. It is always visible in the system prompt, so the agent sees it on every turn without needing to fetch anything. - -### Searchable context - -When you have a large body of information (a knowledge base, documentation, logs, accumulated notes) you do not want to stuff it all into the system prompt. Searchable context keeps a summary in the system prompt (for example, "42 entries indexed") and lets the agent retrieve specific entries when it needs them. - -You provide a provider with a `search()` method. How that search works is entirely up to you: full-text search, vector search via [Vectorize](/vectorize/), a call to an external API, or anything else. The Session does not care about the implementation, only that the provider has a `search()` method. - -The built-in `AgentSearchProvider` uses Durable Object SQLite with FTS5 as default: - - - -```ts -import { AgentSearchProvider } from "agents/experimental/memory/session"; - -const session = Session.create(this).withContext("knowledge", { - description: - "Searchable knowledge base, search for relevant information before answering", - provider: new AgentSearchProvider(this), -}); -``` - - - -But you can implement your own provider backed by any search mechanism: - - - -```ts -const session = Session.create(this).withContext("knowledge", { - description: "Searchable knowledge base", - provider: { - get: async () => "Product documentation and FAQs", - search: async (query) => { - // Use Vectorize, an external API, whatever you need - const results = await env.VECTORIZE_INDEX.query( - await generateEmbedding(query), - { topK: 5 }, - ); - return results.matches.map((m) => m.metadata.text).join("\n\n"); - }, - set: async (key, content) => { - // Index new content - }, - }, -}); -``` - - - -The agent gets a `search_context` tool for querying and a `set_context` tool for indexing new entries. It decides what to search for, and you decide how the search works. - -This is the right choice when the agent needs to find specific pieces of information from a large collection, rather than loading entire documents. - -### Loadable context (Skills) - -Skills are large pieces of context (complete documents, reference guides, runbooks, templates) that the agent can discover and load on demand. Think of them as reference material on a shelf: the agent sees a list of titles and descriptions, picks what is relevant to the current task, loads it, uses it, and unloads it when done. - -Unlike searchable context where the agent retrieves small chunks from a larger collection, skills are designed to be loaded whole. When an agent loads a skill, it gets the entire document in its context window. - -Skills are backed by the `SkillProvider` interface. A skill provider has three methods: - -- **`get()`** returns a metadata listing (titles and descriptions) that appears in the system prompt -- **`load(key)`** fetches the full content of a specific skill -- **`set(key, content, description?)`** writes or updates a skill entry (optional) - -The system prompt shows available skills as a listing. The `[loadable]` tag tells the LLM that these entries are not inline. It needs to use a tool to access the full content: - -```txt -══════════════════════════════════════════════ -SKILLS [loadable] -══════════════════════════════════════════════ -- api-ref: API Reference documentation -- style-guide: Company style guide -- deploy-checklist: Production deployment checklist -``` - -The agent sees the titles, decides which skill is relevant to the current task, and uses `load_context` to pull the full content into its working context. When it is done, it uses `unload_context` to free the space. When the skill provider implements `set()`, the agent can also write back, updating existing skills or creating new ones. - -```txt -Agent sees: "- deploy-checklist: Production deployment checklist" -User asks: "Walk me through a production deployment" -Agent calls: load_context({ block: "skills", key: "deploy-checklist" }) -→ Full checklist content is loaded into the agent's working context -``` - -#### R2-backed skills - -The built-in `R2SkillProvider` stores skills in a Cloudflare R2 bucket. Each skill is an R2 object with optional custom metadata for descriptions. - - - -```ts -import { Session, R2SkillProvider } from "agents/experimental/memory/session"; - -const session = Session.create(this) - .withContext("soul", { - provider: { - get: async () => - [ - "You are a helpful assistant with access to skills.", - "When a user asks you to do something, check the SKILLS section", - "for a relevant skill and use load_context to load it.", - ].join("\n"), - }, - }) - .withContext("memory", { - description: "Learned facts", - maxTokens: 1100, - }) - .withContext("skills", { - provider: new R2SkillProvider(env.SKILLS_BUCKET, { prefix: "skills/" }), - }) - .withCachedPrompt(); -``` - - - -The `prefix` option scopes the provider to a subdirectory in the bucket. Skill keys in the metadata listing are shown without the prefix, so `skills/api-ref` becomes `api-ref` in the system prompt. - -Use `keys` to allowlist specific prefix-relative skills for `get()` and `load()`: - - - -```ts -new R2SkillProvider(env.SKILLS_BUCKET, { - prefix: "skills/", - keys: ["deploy-checklist", "api-ref"], -}); -``` - - - -Add an R2 bucket binding to your Wrangler configuration: - - - -```jsonc -{ - "r2_buckets": [ - { - "binding": "SKILLS_BUCKET", - "bucket_name": "my-agent-skills" - } - ] -} -``` - - - -Skills are regular R2 objects. Upload them through any R2 interface (the Wrangler CLI, the dashboard, or the Workers API): - -```sh -# Upload a skill from a file -wrangler r2 object put my-agent-skills/skills/style-guide --file ./docs/style-guide.md --content-type text/markdown -``` - -To add descriptions (shown in the metadata listing), set custom metadata on the R2 object: - - - -```ts -await env.SKILLS_BUCKET.put("skills/api-ref", content, { - customMetadata: { description: "API Reference documentation" }, -}); -``` - - +## Conversation history -#### Custom skill providers +Each harness keeps its own transcript. For example, the [Pi harness](/agents/harnesses/pi/) stores Pi's transcript in the object's database. You read history through the harness, for example with `harness.messages()`. -You can back skills with any storage by implementing the `SkillProvider` interface: +When you build your own loop, install `Sessions` and store messages in it: ```ts -import type { SkillProvider } from "agents/experimental/memory/session"; - -class DatabaseSkillProvider implements SkillProvider { - private db: D1Database; - - constructor(db: D1Database) { - this.db = db; - } - - async get(): Promise { - const rows = await this.db - .prepare("SELECT key, description FROM skills ORDER BY key") - .all(); - if (rows.results.length === 0) return null; - return rows.results - .map((r) => `- ${r.key}${r.description ? `: ${r.description}` : ""}`) - .join("\n"); - } - - async load(key: string): Promise { - const row = await this.db - .prepare("SELECT content FROM skills WHERE key = ?") - .bind(key) - .first(); - return row ? (row.content as string) : null; - } - - async set(key: string, content: string, description?: string): Promise { - await this.db - .prepare( - "INSERT INTO skills (key, content, description) VALUES (?, ?, ?) " + - "ON CONFLICT(key) DO UPDATE SET content = ?, description = ?", - ) - .bind(key, content, description ?? null, content, description ?? null) - .run(); +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { Sessions } from "agents/sessions"; + +export class ChatAgent extends DurableObject { + readonly sessions = new Sessions(); + readonly lifecycle = Lifecycle.install(this).use(this.sessions); + + async remember(text: string) { + const session = this.sessions.session(); + await session.appendMessage({ + id: crypto.randomUUID(), + role: "user", + parts: [{ type: "text", text }], + }); + return session.getRecentHistory(100_000); } } ``` -The Session detects the `load()` method via duck-typing and generates the appropriate tools automatically. - -#### Skills vs other memory types +Messages form a tree, so a conversation can branch when a user edits a message or regenerates an answer. `Sessions` also stores image and file attachments, searches message text, and reads long histories as a stream or in byte-budgeted windows. -| Aspect | Skills | Writable context | Searchable context | -| -------------------- | ----------------------------------- | ------------------------ | ---------------------------------- | -| **In system prompt** | Metadata listing only | Full content | Summary count | -| **Access pattern** | Load whole document by key | Always visible | Search by query | -| **Best for** | Large documents, reference material | Short notes, preferences | Large collections of small entries | -| **Context cost** | Low (until loaded) | Proportional to content | Low (until searched) | -| **Agent writes?** | Optional (if `set` implemented) | Yes (via `set_context`) | Yes (via `set_context`) | +## Context blocks -The key distinction: skills are **lazy**. They cost nearly nothing in the system prompt until the agent decides it needs one. This makes them ideal for large reference material where only a subset is relevant to any given conversation. +A context block is a labelled section of the system prompt with a provider behind it. What the provider can do decides how the block behaves, and which tools the model gets for it: -## How agents interact with memory - -The Session automatically generates tools based on the provider types of your context blocks. You pass these tools to your LLM alongside your own application-specific tools: - - +| Provider | Block behavior | Tool for the model | +| ----------------------- | -------------------------------------------------------- | ------------------ | +| `get()` | Read-only text, such as the agent's identity | None | +| `get()` and `set()` | Writable memory the model updates, such as user facts | `set_context` | +| `get()` and `search()` | A summary in the prompt, and full-text search on request | `search_context` | ```ts -const sessionTools = await session.tools(); -const allTools = { ...sessionTools, ...myApplicationTools }; - -const result = streamText({ - model: myModel, - system: await session.freezeSystemPrompt(), - messages: await convertToModelMessages(await session.getHistory()), - tools: allTools, -}); -``` - - - -### Generated tools - -The Session generates tools dynamically based on what provider types are present: - -| Tool | Generated when | What it does | -| -------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -| **`set_context`** | Any writable, skill, or search block exists | Writes content to a named block. For writable blocks, replaces or appends. For skill/search blocks, writes a keyed entry. | -| **`load_context`** | Any skill block exists | Loads full content of a document by key into the agent's context. | -| **`unload_context`** | Any skill block exists | Frees context space by removing a previously loaded document. The document remains available for re-loading. | -| **`search_context`** | Any search block exists | Full-text search within a searchable block. Returns the top results ranked by relevance. | -| **`session_search`** | Using `SessionManager` | Searches across all sessions (cross-conversation search). | - -The tools include descriptions and parameter schemas that tell the LLM which blocks are available and what they are for. The agent decides when and how to use them based on the conversation. - -For the full tool signatures and all Session methods, refer to the [Session API reference](/agents/runtime/lifecycle/sessions/). - -## The system prompt - -Context blocks are assembled into a structured system prompt with clear headers and metadata. Each block gets a labeled section with tags indicating its type and capacity: - -```txt -══════════════════════════════════════════════ -SOUL (Identity) [readonly] -══════════════════════════════════════════════ -You are a helpful coding assistant who speaks concisely. - -══════════════════════════════════════════════ -MEMORY (Important facts) [45% — 495/1100 tokens] [writable] -══════════════════════════════════════════════ -User prefers dark mode. -User's project uses React and TypeScript. - -══════════════════════════════════════════════ -KNOWLEDGE (Searchable knowledge base) [searchable] -══════════════════════════════════════════════ -12 entries indexed. - -══════════════════════════════════════════════ -SKILLS [loadable] -══════════════════════════════════════════════ -- api-ref: API Reference documentation -- style-guide: Company style guide -``` - -The tags (`[readonly]`, `[writable]`, `[searchable]`, `[loadable]`) tell the LLM what kind of interaction is possible with each block. Token budgets show the agent how much space remains in writable blocks, helping it manage its own memory. - -## Gotchas - -### Prompt caching - -LLM providers (Anthropic, OpenAI, and others) cache the system prompt prefix. When consecutive requests share the same system prompt, the provider can skip re-processing that prefix, reducing both latency and cost. Breaking the cache (by changing the system prompt) throws away this benefit. - -The Session API is designed to work with prompt caching: - -- **`freezeSystemPrompt()`** renders the system prompt from all context blocks on the first call, then returns the cached value on subsequent calls. The prompt does not change between turns, even when the agent writes to memory via `set_context`. -- **`withCachedPrompt()`** persists the frozen prompt to storage, so it survives Durable Object hibernation and eviction. When the agent wakes up, it loads the same prompt without re-fetching from all providers. - -When the agent uses `set_context` to update a writable block, the underlying provider is updated immediately (the data is saved), but the frozen system prompt is **not** re-rendered. The LLM sees the update on its next turn only if you explicitly call `refreshSystemPrompt()`, which you typically do between conversation turns, not mid-turn. - -This means the system prompt stays stable throughout a multi-step tool-use turn, preserving the provider's prefix cache across every step. - - +import { ContextBlocks } from "agents/context"; -```ts -const session = Session.create(this) - .withContext("soul", { +const context = new ContextBlocks([ + { + label: "soul", provider: { get: async () => "You are a helpful assistant." }, - }) - .withContext("memory", { description: "Learned facts", maxTokens: 1100 }) - .withCachedPrompt(); // Persist the frozen prompt across hibernation - -// During a conversation turn: -const system = await session.freezeSystemPrompt(); // Same value every call -const tools = await session.tools(); - -// ... agent calls set_context to update memory ... -// The frozen prompt is NOT changed, prefix cache stays warm + }, + { + label: "memory", + description: "Facts learned about the user", + maxTokens: 1_100, + provider: memoryProvider, + }, +]); -// Between turns (optional, if you want the agent to see its own updates): -await session.refreshSystemPrompt(); +const system = await context.freezeSystemPrompt(); +const tools = await context.tools(); ``` - - -### Compaction - -Long conversations eventually exceed the LLM's context window. Compaction addresses this at two levels: **macro-compaction** summarizes ranges of older messages, and **micro-compaction** truncates individual messages that are too large. +Each block renders with a header that carries its label, its description, how much of its `maxTokens` budget it uses, and a marker such as `[readonly]`, `[writable]`, or `[searchable]`. The markers and budgets tell the model what it can do with each block, so it can manage its own memory. -#### Macro-compaction +`agents/context` includes two durable providers. `AgentContextProvider` stores one writable block per row in SQLite, and `AgentSearchProvider` backs a searchable block with a full-text index. Both take anything with a tagged-template `sql` method, such as an `Agent`. -Macro-compaction summarizes older messages, but it never deletes the originals. +## Frozen prompts and caching -It uses **overlays**: a summary is stored in a separate table, keyed by the message range it covers. When `getHistory()` is called, overlays are applied transparently at read time. The compacted range is replaced by a synthetic summary message. The underlying messages remain in SQLite, preserving the full conversation for audit, search, and branching. +Model providers cache the prefix of a request. If the system prompt changes between turns, the cache misses and every turn pays for the full prompt again. -```txt -Messages: [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] - ↓ compaction ↓ -Overlay: [1] [2] [SUMMARY of 3-7] [8] [9] [10] - ↑ tail protected -``` +`freezeSystemPrompt()` renders the prompt once and returns the same string on every later call. A write to a block goes to its provider immediately, but it does not change the frozen prompt until you call `refreshSystemPrompt()`. Pass a writable provider as the second argument to `ContextBlocks` to store the frozen prompt, so a restarted agent reuses the exact prompt the model already cached. -The key points: +For a block that changes while the prompt is frozen, such as the current date, set `whenChanged: "remind"`. `reminder()` then returns the block's new value for you to send after the cached prefix, for example at the end of the latest user message, until the next refresh. -- **Non-destructive**, original messages are never deleted. The full conversation is always available in the database. -- **Iterative**, when the conversation grows again and triggers another compaction, the existing summary is passed to the LLM to update, not replaced from scratch. -- **Boundary-aware**, compaction boundaries are shifted to avoid splitting tool call / tool result pairs. -- **Configurable**, `protectHead` preserves the first N messages (usually the system context), and `tailTokenBudget` keeps the most recent messages intact. +## Compaction - +Long conversations eventually exceed the model's context window. Compaction replaces a range of older messages with a summary when you read history. The original messages stay in storage: ```ts -import { createCompactFunction } from "agents/experimental/memory/utils/compaction-helpers"; +import { createCompactFunction } from "agents/sessions"; -const session = Session.create(this) - .withContext("memory", { maxTokens: 1100 }) +session .onCompaction( createCompactFunction({ - summarize: (prompt) => - generateText({ model: myModel, prompt }).then((r) => r.text), - protectHead: 3, - tailTokenBudget: 20000, - minTailMessages: 2, + summarize: async (prompt) => summarizeWithModel(prompt), + keepRecentTokens: 20_000, }), ) - .compactAfter(100_000); // Auto-compact when token estimate exceeds threshold -``` - - - -Auto-compaction triggers after `appendMessage()` when the estimated token count exceeds the threshold. Compaction failure is non-fatal, the message is already saved. - -#### Micro-compaction - -Micro-compaction works at the individual message level rather than across ranges. It handles two problems: - -**Read-time truncation**: `truncateOlderMessages()` shortens tool outputs and long text in older messages before sending them to the LLM. Recent messages (last 4 by default) are kept intact. This operates on a copy, stored messages are not mutated. - - - -```ts -import { truncateOlderMessages } from "agents/experimental/memory/utils"; - -const history = await session.getHistory(); -const truncated = truncateOlderMessages(history); -// Pass truncated history to the LLM + .compactAfter(80_000); ``` - +`compactAfter()` compacts once the session's estimated token count passes the limit. -**Row size enforcement**: when a message is persisted (typically an assistant message with large tool outputs), it is checked against the SQLite row size limit. Oversized tool outputs are replaced with a preview and a note suggesting the tool be re-run. This prevents individual messages from exceeding storage limits while preserving the conversation flow. +Refresh the frozen system prompt after a compaction, so the prompt and the summarized history agree. -## Related +## Related resources diff --git a/src/content/docs/agents/concepts/what-are-agents.mdx b/src/content/docs/agents/concepts/what-are-agents.mdx index 2784b2271c9..81fe348108d 100644 --- a/src/content/docs/agents/concepts/what-are-agents.mdx +++ b/src/content/docs/agents/concepts/what-are-agents.mdx @@ -79,13 +79,13 @@ Agents operate in a continuous loop of: ## Building agents on Cloudflare -The Cloudflare Agents SDK provides the infrastructure for building production agents: +On Cloudflare, an agent is a [harness](/agents/harnesses/) running on a [Durable Object](/durable-objects/), with the [capabilities](/agents/capabilities/) you plug in: -- **Persistent state** — Each agent instance has its own SQLite database for storing context and memory -- **Real-time sync** — State changes automatically broadcast to all connected clients via WebSockets -- **Hibernation** — Agents sleep when idle and wake on demand, so you only pay for what you use -- **Global edge deployment** — Agents run close to your users on Cloudflare's network -- **Built-in capabilities** — Scheduling, task queues, workflows, email handling, and more +- **A harness**: the [Pi harness](/agents/harnesses/pi/) runs the observe, plan, and execute loop for you. +- **Persistent state**: each agent instance has its own SQLite database for context and memory. +- **Durable work**: the [Lifecycle](/agents/lifecycle/) keeps a job queue that wakes the agent after eviction, deploys, and crashes. +- **Capabilities**: MCP clients, schedules, queues, and real-time WebSocket clients plug in on the same object. +- **Hibernation**: agents sleep when idle and wake on demand, so you only pay for what you use. ## Next steps @@ -96,9 +96,9 @@ The Cloudflare Agents SDK provides the infrastructure for building production ag /> diff --git a/src/content/docs/agents/getting-started/add-to-existing-project.mdx b/src/content/docs/agents/getting-started/add-to-existing-project.mdx index db87dd45b23..553b4987bed 100644 --- a/src/content/docs/agents/getting-started/add-to-existing-project.mdx +++ b/src/content/docs/agents/getting-started/add-to-existing-project.mdx @@ -1,6 +1,6 @@ --- -title: Add to existing project -description: Add the Agents SDK to an existing Cloudflare Workers project with state management and real-time connections. +title: Add to an existing project +description: Run an agent in an existing Worker or Durable Object. Install the Lifecycle, add a harness and capabilities, and move fetch, alarm, and WebSocket handlers onto the Lifecycle. pcx_content_type: how-to sidebar: order: 2 @@ -10,340 +10,212 @@ products: import { PackageManagers, + Steps, TypeScriptExample, WranglerConfig, - Render, } from "~/components"; -This guide shows how to add agents to an existing Cloudflare Workers project. If you are starting fresh, refer to [Building a chat agent](/agents/examples/chat-agent/) instead. +An agent is a [harness](/agents/harnesses/) running on a Durable Object. Any Durable Object can run one once it installs the [Lifecycle](/agents/lifecycle/). This guide covers adding a new agent to an existing Worker, and moving an existing Durable Object onto the Lifecycle so it can run a harness and plug in [capabilities](/agents/capabilities/). -## Prerequisites - -- An existing Cloudflare Workers project with a Wrangler configuration file -- Node.js 18 or newer - -## 1. Install the package +## Install the package -For React applications, no additional packages are needed — React bindings are included. - -For Hono applications: - - - -## 2. Create an Agent - -Create a new file for your agent (for example, `src/agents/counter.ts`): - - - -```ts -import { Agent, callable } from "agents"; - -export type CounterState = { - count: number; -}; - -export class CounterAgent extends Agent { - initialState: CounterState = { count: 0 }; - - @callable() - increment() { - this.setState({ count: this.state.count + 1 }); - return this.state.count; - } - - @callable() - decrement() { - this.setState({ count: this.state.count - 1 }); - return this.state.count; - } -} -``` - - - -## 3. Update Wrangler configuration - -Add the Durable Object binding and migration: +The `agents` package requires the `nodejs_compat` compatibility flag: ```jsonc { - "name": "my-existing-project", - "main": "src/index.ts", - "upload_source_maps": true, - "compatibility_date": "$today", "compatibility_flags": ["nodejs_compat"], - - "durable_objects": { - "bindings": [ - { - "name": "CounterAgent", - "class_name": "CounterAgent", - }, - ], - }, - - "migrations": [ - { - "tag": "v1", - "new_sqlite_classes": ["CounterAgent"], - }, - ], } ``` -**Key points:** +## Add a new agent to a Worker -- `name` in bindings becomes the property on `env` (for example, `env.CounterAgent`) -- `class_name` must exactly match your exported class name -- `new_sqlite_classes` enables SQLite storage for state persistence -- `nodejs_compat` flag is required for the agents package + -## 4. Configure TypeScript and Vite +1. Add a SQLite-backed Durable Object binding and migration to your Wrangler configuration file: -If you use `@callable()` decorators (as in the example above), you need two build configurations. + -**tsconfig.json** — extend `agents/tsconfig` (or set `"target": "ES2021"` manually): + ```jsonc + { + "durable_objects": { + "bindings": [{ "name": "SupportAgent", "class_name": "SupportAgent" }], + }, + "migrations": [{ "tag": "v1", "new_sqlite_classes": ["SupportAgent"] }], + } + ``` -```json -{ - "extends": "agents/tsconfig" -} -``` + -If you have an existing `tsconfig.json` with custom settings, you can extend and override: + If your project already has migrations, add a new tag instead of reusing `v1`. -```json -{ - "extends": "agents/tsconfig", - "compilerOptions": { - "paths": { "~/*": ["./src/*"] } - } -} -``` +2. Create the agent and export it from your Worker's entry point: -**vite.config.ts** — add the `agents()` plugin (handles TC39 decorator transforms for Vite 8): + - + ```ts + import { DurableObject } from "cloudflare:workers"; + import { Lifecycle } from "agents/lifecycle"; -```ts -import agents from "agents/vite"; - -export default defineConfig({ - plugins: [ - agents(), - // ... your existing plugins - ], -}); -``` + export class SupportAgent extends DurableObject { + readonly lifecycle = Lifecycle.install(this); - + onRequest(request: Request): Response { + return new Response(`Support agent ${this.lifecycle.name}`); + } + } + ``` -If your project does not use Vite, the `tsconfig.json` change alone is sufficient — your bundler must support TC39 decorators (stage 3, version `2023-11`). + -For more details, refer to the [TypeScript configuration](/agents/runtime/operations/configuration/#typescript-configuration) and [Vite configuration](/agents/runtime/operations/configuration/#vite-configuration) reference. + ```ts title="src/index.ts" + export { SupportAgent } from "./support-agent"; + ``` -## 5. Export the Agent class +3. Route requests to the agent from your Worker's `fetch` handler. `routeAgentRequest()` returns `null` for paths it does not handle, so your existing routes keep working: -Your agent class must be exported from your main entry point. Update your `src/index.ts`: + - + ```ts + import { routeAgentRequest } from "agents"; -```ts -// Export the agent class (required for Durable Objects) -export { CounterAgent } from "./agents/counter"; + export { SupportAgent } from "./support-agent"; -// Your existing exports... -export default { - // ... -} satisfies ExportedHandler; -``` + export default { + async fetch(request: Request, env: Env): Promise { + const agentResponse = await routeAgentRequest(request, env); + if (agentResponse) return agentResponse; - + // Your existing routing. + return new Response("Not found", { status: 404 }); + }, + } satisfies ExportedHandler; + ``` -## 6. Wire up routing + -Choose the approach that matches your project structure: + Requests to `/agents/support-agent/:name` now reach the agent. To call it from your own routes instead, use `env.SupportAgent.getByName(name).fetch(request)`. -### Plain Workers (fetch handler) +4. Generate types for the new binding: - + ```sh + npx wrangler types + ``` -```ts -import { routeAgentRequest } from "agents"; -export { CounterAgent } from "./agents/counter"; - -export default { - async fetch(request: Request, env: Env, ctx: ExecutionContext) { - // Try agent routing first - const agentResponse = await routeAgentRequest(request, env); - if (agentResponse) return agentResponse; - - // Your existing routing logic - const url = new URL(request.url); - if (url.pathname === "/api/hello") { - return Response.json({ message: "Hello!" }); - } - - return new Response("Not found", { status: 404 }); - }, -} satisfies ExportedHandler; -``` - - + -### Hono +The object is ready to run an agent. Install a harness with `.use()`, as in the [Quick start](/agents/getting-started/quick-start/#run-a-harness-on-a-durable-object), then plug in the [capabilities](/agents/capabilities/) you need. - - -```ts -import { Hono } from "hono"; -import { agentsMiddleware } from "hono-agents"; -export { CounterAgent } from "./agents/counter"; +## Move an existing Durable Object onto the Lifecycle -const app = new Hono<{ Bindings: Env }>(); +`Lifecycle.install(this)` installs the `fetch`, `alarm`, `webSocketMessage`, `webSocketClose`, and `webSocketError` handlers. Move the logic out of those methods before you install it. -// Add agents middleware - handles WebSocket upgrades and agent HTTP requests -app.use("*", agentsMiddleware()); - -// Your existing routes continue to work -app.get("/api/hello", (c) => c.json({ message: "Hello!" })); - -export default app; -``` - - +### Move `fetch()` to `onRequest()` -### With static assets - -If you are serving static assets alongside agents, static assets are served first by default. Your Worker code only runs for paths that do not match a static asset: +Rename `fetch()` to `onRequest()`. Lifecycle calls it for every request that no capability handles: ```ts -import { routeAgentRequest } from "agents"; -export { CounterAgent } from "./agents/counter"; - -export default { - async fetch(request: Request, env: Env, ctx: ExecutionContext) { - // Static assets are served automatically before this runs - // This only handles non-asset requests - - // Route to agents - const agentResponse = await routeAgentRequest(request, env); - if (agentResponse) return agentResponse; - - return new Response("Not found", { status: 404 }); - }, -} satisfies ExportedHandler; -``` +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; - - -Configure assets in the Wrangler configuration file: +export class Counter extends DurableObject { + readonly lifecycle = Lifecycle.install(this); - - -```jsonc -{ - "assets": { - "directory": "./public", - }, + // Previously: async fetch(request: Request) + async onRequest(request: Request): Promise { + const count = ((await this.ctx.storage.get("count")) ?? 0) + 1; + await this.ctx.storage.put("count", count); + return Response.json({ count }); + } } ``` - - -## 7. Generate TypeScript types - -Do not hand-write your `Env` interface. Run [`wrangler types`](/workers/wrangler/commands/general/#types) to generate a type definition file that matches your Wrangler configuration. This catches mismatches between your config and code at compile time instead of at deploy time. - -Re-run `wrangler types` whenever you add or rename a binding. - -```sh -npx wrangler types -``` - -This creates a type definition file with all your bindings typed, including your agent Durable Object namespaces. The `Agent` class defaults to using the generated `Env` type, so you do not need to pass it as a type parameter — `extends Agent` is sufficient unless you need to pass a second type parameter for state (for example, `Agent`). + -Refer to [Configuration](/agents/runtime/operations/configuration/#generating-types) for more details on type generation. +Your existing storage stays where it is. Capabilities create their own tables, with prefixes such as `cf_agents_` and `pi_`. -## 8. Connect from the frontend +### Replace `alarm()` with jobs -### React +Lifecycle owns the object's single alarm. Replace `ctx.storage.setAlarm()` with a job, and move the alarm logic to `onJob()`: -```tsx -import { useState } from "react"; -import { useAgent } from "agents/react"; -import type { CounterAgent, CounterState } from "./agents/counter"; - -function CounterWidget() { - const [count, setCount] = useState(0); - - const agent = useAgent({ - agent: "CounterAgent", - onStateUpdate: (state) => setCount(state.count), - }); +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle, type LifecycleJobContext } from "agents/lifecycle"; + +export class Counter extends DurableObject { + readonly lifecycle = Lifecycle.install(this); + + async onRequest(): Promise { + // Previously: await this.ctx.storage.setAlarm(Date.now() + 60_000); + await this.lifecycle.jobs.push({ + id: "reset", + fn: "reset", + time: Date.now() + 60_000, + }); + return new Response("Reset scheduled", { status: 202 }); + } - return ( - <> - {count} - - - - ); + // Previously: async alarm() + async onJob({ job }: LifecycleJobContext): Promise { + if (job.fn === "reset") await this.ctx.storage.delete("count"); + } } ``` -Key points: +For recurring or cron work, install the [`Scheduler`](/agents/capabilities/#scheduler) capability instead. For more information, refer to [Jobs and alarms](/agents/lifecycle/job-queue/). -- `useAgent` connects to your agent via WebSocket -- `onStateUpdate` fires whenever the agent's state changes -- `agent.stub.methodName()` calls methods marked with `@callable()` on your agent +### Move WebSocket handlers to the `WebSockets` capability -### Vanilla JavaScript +Lifecycle ignores sockets you accept yourself with `ctx.acceptWebSocket()`. Install the `WebSockets` capability and move your handlers into it: ```ts -import { AgentClient } from "agents/client"; - -const agent = new AgentClient({ - agent: "CounterAgent", - name: "user-123", // Optional: unique instance name - onStateUpdate: (state) => { - document.getElementById("count").textContent = state.count; - }, -}); - -// Call methods -document.getElementById("increment").onclick = () => agent.call("increment"); +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { WebSockets } from "agents/websockets"; + +export class ChatRoom extends DurableObject { + readonly webSockets = new WebSockets({ + handlers: { + // Previously: webSocketMessage(ws, message) + onMessage: (_connection, message) => { + for (const connection of this.webSockets.getConnections()) { + connection.send(message); + } + }, + }, + }); + readonly lifecycle = Lifecycle.install(this).use(this.webSockets); +} ``` - +Connections still hibernate. Clients can connect with a plain `WebSocket` or with `useAgent` from `agents/react`. -## Deploy to Cloudflare +### Start the Lifecycle in RPC methods -```sh -npm run deploy -``` +RPC methods do not pass through `fetch`. Call `await this.lifecycle.start()` at the top of any RPC method that uses a capability before the object has started. -Your agent is now live on Cloudflare's global network, running close to your users. +### Address objects by name - +Lifecycle requires a named object. If your code creates IDs with `newUniqueId()`, switch to `getByName()` or `idFromName()`. Objects created with unique IDs cannot move onto the Lifecycle, because their IDs carry no name. - +## Next steps - +- [Harnesses](/agents/harnesses/) compares the harnesses you can run. +- [Capabilities](/agents/capabilities/) lists the capabilities you can plug in. +- [Quick start](/agents/getting-started/quick-start/) builds an agent with the Pi harness. +- [Add an Agent to an existing project](/agents/agent-class/add-to-existing-project/) covers the same steps for the legacy `Agent` class. diff --git a/src/content/docs/agents/getting-started/index.mdx b/src/content/docs/agents/getting-started/index.mdx index 68b3d6adae4..c036bb238b8 100644 --- a/src/content/docs/agents/getting-started/index.mdx +++ b/src/content/docs/agents/getting-started/index.mdx @@ -1,6 +1,6 @@ --- title: Getting started -description: Start building Agents on Cloudflare that maintain state, make decisions, and execute workflows. +description: Start running agents on Cloudflare. Pick a harness, run it on a Durable Object, and plug in the capabilities you need. pcx_content_type: overview sidebar: order: 1 @@ -12,26 +12,26 @@ products: import { DirectoryListing, LinkCard } from "~/components"; -Start building agents that can remember context, communicate with users, and act on their own. Pick the path that matches what you want to build first. +An agent on Cloudflare is a [harness](/agents/harnesses/) running on a Durable Object, with the [capabilities](/agents/capabilities/) you plug in. Pick the path that matches what you want to build first. ## Choose a path +To build on the legacy `Agent` class instead, refer to [Get started with the Agent class](/agents/agent-class/get-started/). + ## All getting started pages diff --git a/src/content/docs/agents/getting-started/quick-start.mdx b/src/content/docs/agents/getting-started/quick-start.mdx index e19e76425c9..b05b08f3723 100644 --- a/src/content/docs/agents/getting-started/quick-start.mdx +++ b/src/content/docs/agents/getting-started/quick-start.mdx @@ -3,114 +3,120 @@ title: Quick start pcx_content_type: get-started sidebar: order: 1 -description: Build your first agent in 10 minutes — a counter with persistent state that syncs to a React frontend in real-time. +description: Run your first agent on Cloudflare. Install the Pi harness on a Durable Object, plug in a scheduler, and deploy it. products: - agents --- import { + PackageManagers, + Steps, TypeScriptExample, WranglerConfig, - PackageManagers, - Render, } from "~/components"; -Build AI agents that persist, think, and act. Agents run on Cloudflare's global network, maintain state across requests, and connect to clients in real-time via WebSockets. +An agent on Cloudflare is a [harness](/agents/harnesses/) running on a [Durable Object](/durable-objects/). The [Lifecycle](/agents/lifecycle/) keeps the harness running, and you plug in the [capabilities](/agents/capabilities/) it needs. In this guide, you run the [Pi harness](/agents/harnesses/pi/) on Workers AI, answer prompts over HTTP, and plug in a scheduler that asks for a plan every morning. -**What you will build:** A counter agent with persistent state that syncs to a React frontend in real-time. +You do not need an API key. The agent uses [Workers AI](/workers-ai/) through the `AI` binding. -**Time:** ~10 minutes +## Prerequisites -## Create a new project +- A [Cloudflare account](https://dash.cloudflare.com/sign-up) +- Node.js 24 or later - +## Create a project -Then install dependencies and start the dev server: + -```sh -cd agents-starter -npm install -npm run dev -``` +1. Create a Worker project: -This creates a project with: + -- `src/server.ts` — Your agent code -- `src/client.tsx` — React frontend -- `wrangler.jsonc` — Cloudflare configuration -- `tsconfig.json` — Extends `agents/tsconfig` for correct decorator and module settings -- `vite.config.ts` — Includes the `agents/vite` plugin for decorator support +2. Go to the project directory and install the Agents SDK and the Pi packages: -The starter template includes two important SDK integrations. If you are setting up a project manually, add both: + ```sh + cd my-agent + ``` -**tsconfig.json** — extends `agents/tsconfig`, which sets `target: "ES2021"` and other recommended options: + -```json -{ - "extends": "agents/tsconfig" -} -``` + -**vite.config.ts** — includes the `agents()` plugin, which handles TC39 decorator transforms (required for `@callable()` in Vite 8): +## Configure Wrangler -```ts -import { cloudflare } from "@cloudflare/vite-plugin"; -import react from "@vitejs/plugin-react"; -import agents from "agents/vite"; -import { defineConfig } from "vite"; - -export default defineConfig({ - plugins: [agents(), react(), cloudflare()], -}); -``` +Add the `AI` binding and a SQLite-backed Durable Object to your Wrangler configuration file: -Open [http://localhost:5173](http://localhost:5173) to see your agent in action. + -## Your first agent +```jsonc +{ + "name": "my-agent", + "main": "src/index.ts", + "upload_source_maps": true, + "compatibility_date": "$today", + "compatibility_flags": ["nodejs_compat"], + "ai": { + "binding": "AI", + }, + "durable_objects": { + "bindings": [{ "name": "Assistant", "class_name": "Assistant" }], + }, + "migrations": [{ "tag": "v1", "new_sqlite_classes": ["Assistant"] }], +} +``` -Build a simple counter agent from scratch. Replace `src/server.ts`: + - +The `nodejs_compat` flag is required by the `agents` package. `new_sqlite_classes` gives the Durable Object the SQLite storage that the Lifecycle and the harness use. -```ts title="src/server.ts" -import { Agent, routeAgentRequest, callable } from "agents"; +## Run a harness on a Durable Object -// Define the state shape -export type CounterState = { - count: number; -}; +Replace `src/index.ts` with an `Assistant` Durable Object that runs the Pi harness: -// Create the agent -export class CounterAgent extends Agent { - // Initial state for new instances - initialState: CounterState = { count: 0 }; + - // Methods marked with @callable can be called from the client - @callable() - increment() { - this.setState({ count: this.state.count + 1 }); - return this.state.count; - } +```ts +import { DurableObject } from "cloudflare:workers"; +import { createModels } from "@earendil-works/pi-ai/models"; +import { createRegistry, Harness } from "@earendil-works/pi-durable"; +import { routeAgentRequest } from "agents"; +import { PiHarness } from "agents/harness/pi"; +import { Lifecycle } from "agents/lifecycle"; +import { createAI } from "agents/models/pi-ai"; + +export class Assistant extends DurableObject { + readonly ai = createAI({ binding: this.env.AI }); + readonly registry = createRegistry(); + + readonly harness = new PiHarness({ + harness: ({ storage, context }) => { + const models = createModels(); + models.setProvider(this.ai.provider); + return Harness.open(storage, { models, registry: this.registry }, context); + }, + defaults: { + model: this.ai("@cf/zai-org/glm-4.7-flash"), + thinkingLevel: "low", + }, + }); - @callable() - decrement() { - this.setState({ count: this.state.count - 1 }); - return this.state.count; - } + readonly lifecycle = Lifecycle.install(this).use(this.harness); - @callable() - reset() { - this.setState({ count: 0 }); + async onRequest(request: Request): Promise { + if (request.method !== "POST") { + return Response.json(await this.harness.messages()); + } + const { status, text } = await this.harness.prompt(await request.text()); + return Response.json({ status, text }); } } -// Route requests to agents export default { - async fetch(request: Request, env: Env, ctx: ExecutionContext) { + async fetch(request: Request, env: Env): Promise { return ( (await routeAgentRequest(request, env)) ?? new Response("Not found", { status: 404 }) @@ -121,122 +127,105 @@ export default { -Update `wrangler.jsonc` to register the agent: +Here is what each part does: - +- `Assistant` extends the platform `DurableObject`. Each named instance is one agent with its own SQLite database. +- `PiHarness` is the harness. It runs the [Pi](https://pi.dev/) agent loop, stores the transcript in the object's database, and keeps runs going after the object is evicted. +- `Lifecycle.install(this)` takes over the object's `fetch` and `alarm` handlers. `.use(this.harness)` runs the harness on the object. +- `onRequest()` handles requests that no capability claimed. A `POST` sends a prompt, and a `GET` returns the transcript. +- `routeAgentRequest()` sends `/agents/assistant/:name` to the `Assistant` instance called `:name`. -```jsonc -{ - "name": "my-agent", - "main": "src/server.ts", - "upload_source_maps": true, - "compatibility_date": "$today", - "compatibility_flags": ["nodejs_compat"], - "durable_objects": { - "bindings": [ - { - "name": "CounterAgent", - "class_name": "CounterAgent", - }, - ], - }, - "migrations": [ - { - "tag": "v1", - "new_sqlite_classes": ["CounterAgent"], - }, - ], -} -``` +Generate types for the `Env` interface: - +```sh +npx wrangler types +``` -**Key points:** +## Run the agent locally -- `name` in bindings becomes the property on `env` (for example, `env.CounterAgent`) -- `class_name` must exactly match your exported class name -- `new_sqlite_classes` enables SQLite storage for state persistence -- `nodejs_compat` flag is required for the agents package + -## Connect from React +1. Start the development server: -Replace `src/client.tsx`: + ```sh + npx wrangler dev + ``` -```tsx title="src/client.tsx" -import "./styles.css"; -import { createRoot } from "react-dom/client"; -import { useState } from "react"; -import { useAgent } from "agents/react"; -import type { CounterAgent, CounterState } from "./server"; + The `AI` binding always runs remotely, so Wrangler asks you to log in the first time. -export default function App() { - const [count, setCount] = useState(0); +2. In another terminal, send the agent a prompt: - // Connect to the Counter agent - const agent = useAgent({ - agent: "CounterAgent", - onStateUpdate: (state) => setCount(state.count), - }); + ```sh + curl -X POST http://localhost:8787/agents/assistant/demo \ + -d "What is 47 times 19? Reply with the number only." + ``` - return ( -
-

Counter Agent

-

{count}

-
- - - -
-
- ); -} + ```json output + { "status": "done", "text": "893" } + ``` -const root = createRoot(document.getElementById("root")!); -root.render(); -``` +3. Read the transcript: -Key points: + ```sh + curl http://localhost:8787/agents/assistant/demo + ``` -- `useAgent` connects to your agent via WebSocket -- `onStateUpdate` fires whenever the agent's state changes -- `agent.stub.methodName()` calls methods marked with `@callable()` on your agent +
- +The transcript lives in the `demo` instance's database. A request to `/agents/assistant/another-name` reaches a different agent with an empty transcript. -## Connect from vanilla JavaScript +## Plug in a capability -If you are not using React: +The harness and its capabilities share the same Lifecycle, so plugging one in is a new field and one more `.use()` call. Add a `Scheduler` that asks the agent for a plan every morning: - + ```ts -import { AgentClient } from "agents/client"; +// ... +import { Scheduler } from "agents/schedules"; + +export class Assistant extends DurableObject { + readonly ai = createAI({ binding: this.env.AI }); + readonly registry = createRegistry(); + readonly harness = new PiHarness({ /* ... */ }); + + readonly scheduler = new Scheduler({ + callbacks: { + morningPlan: async () => { + await this.harness.submit("Write a two-line plan for today."); + }, + }, + }); -const agent = new AgentClient({ - agent: "CounterAgent", - name: "my-counter", // optional, defaults to "default" - onStateUpdate: (state) => { - console.log("New count:", state.count); - }, -}); + readonly lifecycle = Lifecycle.install(this).use(this.harness).use(this.scheduler); + + async onStart(): Promise { + await this.scheduler.set("0 8 * * *", "morningPlan"); + } -// Call methods -await agent.call("increment"); -await agent.call("reset"); + // onRequest() is unchanged. +} ``` -## Deploy to Cloudflare +- `onStart()` runs once each time the object starts. A cron schedule with the same callback and payload already exists after the first start, so `set()` does not add a second one. +- `harness.submit()` returns once Pi has stored the prompt, before the model runs. The harness finishes the run in the background and wakes the object if it is evicted. +- `Scheduler` and `PiHarness` both store their future work in the Lifecycle's job queue, so they share the object's single alarm. + +## Deploy + +Deploy the Worker to Cloudflare: ```sh -npm run deploy +npx wrangler deploy ``` -Your agent is now live on Cloudflare's global network, running close to your users. - - +Your agent is now live at `https://my-agent..workers.dev/agents/assistant/demo`. - +## Next steps - +- To stream Pi sessions to a browser over WebSockets, refer to [Pi harness](/agents/harnesses/pi/). +- To plug in MCP servers, queues, or connected clients, refer to [Capabilities](/agents/capabilities/). +- To compare Pi with Project Think or a harness of your own, refer to [Harnesses](/agents/harnesses/). +- To understand startup, request handling, and jobs, refer to [Lifecycle](/agents/lifecycle/). diff --git a/src/content/docs/agents/harnesses/ai-sdk.mdx b/src/content/docs/agents/harnesses/ai-sdk.mdx new file mode 100644 index 00000000000..8700328fe08 --- /dev/null +++ b/src/content/docs/agents/harnesses/ai-sdk.mdx @@ -0,0 +1,106 @@ +--- +title: AI SDK harness +pcx_content_type: how-to +description: Run an AI SDK streamText loop on a Durable Object with the experimental AiSdkHarness capability, with sessions, transcripts, and a queue of operations in the object's storage. +sidebar: + order: 3 + badge: Experimental +products: + - agents +--- + +import { PackageManagers, TypeScriptExample } from "~/components"; + +`AiSdkHarness` from `agents/harness/ai-sdk` runs the [AI SDK](https://ai-sdk.dev/)'s `streamText` as a harness on a Durable Object. It keeps sessions, their transcripts, and a queue of operations in the object's storage. One operation runs at a time per session, and each run is one `streamText` call over the session's transcript. + +:::caution[Experimental] +`AiSdkHarness` is experimental. The API may change in any release. +::: + +Use it when you want a thin, AI SDK-native loop. `AiSdkHarness` implements the shared harness interface that the experimental Channels API serves, so the same harness can answer browsers, terminals, and messaging platforms. + +## Install + + + +The example uses [Workers AI](/workers-ai/) through the `AI` binding and `createAI` from `agents/models/ai-sdk`. To use other models, refer to [AI SDK models](/agents/models/ai-sdk/). + +## Create the harness + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { stepCountIs, tool } from "ai"; +import { z } from "zod"; +import { AiSdkHarness } from "agents/harness/ai-sdk"; +import { Lifecycle } from "agents/lifecycle"; +import { createAI } from "agents/models/ai-sdk"; + +export class Assistant extends DurableObject { + readonly ai = createAI({ binding: this.env.AI }); + + readonly harness = new AiSdkHarness({ + model: this.ai("@cf/moonshotai/kimi-k2.7-code"), + system: "You are a helpful assistant.", + tools: { + flipCoin: tool({ + description: "Flip a coin, once the user approves", + inputSchema: z.object({}), + needsApproval: true, + execute: async () => (Math.random() < 0.5 ? "Heads" : "Tails"), + }), + }, + stopWhen: stepCountIs(5), + }); + + readonly lifecycle = Lifecycle.install(this).use(this.harness); + + async onRequest(request: Request): Promise { + const session = this.harness.session(); + const { operationId } = await session.submit({ + parts: [{ type: "text", text: await request.text() }], + }); + return Response.json(await session.wait(operationId, request.signal)); + } +} +``` + + + +| Option | Description | +| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `model` | Required. An AI SDK `LanguageModel`. | +| `tools` | An AI SDK `ToolSet`. Tools without `execute` run on the client of the participant whose message started the turn. | +| `system` | The system prompt. | +| `stopWhen` | When the run stops calling the model. Defaults to `stepCountIs(5)`. | +| `heartbeatMs` | How often a running session's wake job checks in. Defaults to 30 seconds. | + +## Work with sessions + +`harness.session(id)` returns a handle on one session. Without an id, it returns the default session. Each session has: + +| Method | Description | +| ---------------------------- | ------------------------------------------------------------------------------------------------------- | +| `submit(input, options)` | Queue a message or a tool answer. Pass `operationId` to make the call idempotent. | +| `wait(operationId, signal?)` | Resolve when the operation settles, as `done` with its `text` or `unanswered` with a `reason`. | +| `abort(operationId?)` | Stop one operation, or everything in the session. | +| `reset(handoff?)` | Start a new context, optionally from a handoff note. | +| `watch()` | The current state, then every change in order: operation status, run start and end, chunks, and saved messages. | + +`harness.sessions` has `create()`, `fork(from)`, and `list()`. + +A tool call that needs approval settles the operation. Submit the answer as a new operation to continue: + +```ts +await session.submit({ type: "approval", approvalId, approved: true }); +``` + +## Recovery + +The AI SDK keeps no state of its own. If an eviction cuts a run short, the harness starts the run again from the saved transcript, and the run's unsaved output is lost. Steering is not supported: `whenBusy: "steer"` queues the input as a follow-up. + +## Related resources + +- [Harnesses](/agents/harnesses/) compares the harnesses you can run. +- [AI SDK models](/agents/models/ai-sdk/) configures Workers AI and third-party models. diff --git a/src/content/docs/agents/harnesses/container.mdx b/src/content/docs/agents/harnesses/container.mdx new file mode 100644 index 00000000000..e44d50b9cd5 --- /dev/null +++ b/src/content/docs/agents/harnesses/container.mdx @@ -0,0 +1,144 @@ +--- +title: Container harness +pcx_content_type: how-to +description: Run a coding agent CLI such as Claude Code or Codex in a Cloudflare Container, driven from a Durable Object with the experimental ContainerHarness capability. +sidebar: + order: 4 + badge: Experimental +products: + - agents + - containers +--- + +import { TypeScriptExample, WranglerConfig } from "~/components"; + +`ContainerHarness` from `agents/harness/container` runs an agent CLI, such as Claude Code or Codex, in a [Cloudflare Container](/containers/) and drives it from a Durable Object. It has the same interface as the [Pi harness](/agents/harnesses/pi/): `prompt()`, `submit()`, `wait()`, `abort()`, `messages()`, `pending()`, `session(id)`, and `sessions`. + +:::caution[Experimental] +`ContainerHarness` is experimental. The API may change before it stabilizes. It uses the Containers [`durable_object` scheduling policy](/containers/configuration/scheduling-policy/), which is in beta. +::: + +The Durable Object keeps everything durable: sessions, operations, the transcript, and the CLI's own session files. The container is disposable. It stops after an idle timeout, and the next prompt starts a new container in which the CLI resumes its session. + +## Configure Wrangler + +Each agent object gets its own container through `ctx.container`. Use the `durable_object` scheduling policy. You do not need an image: the harness builds the container from the [`cloudflare/debian-trixie`](/containers/guides/image-management/#use-the-cloudflare-managed-image) image at runtime. + + + +```jsonc +{ + "name": "coding-agent", + "main": "src/index.ts", + "upload_source_maps": true, + "compatibility_date": "$today", + "compatibility_flags": ["nodejs_compat"], + "durable_objects": { + "bindings": [{ "name": "CodingAgent", "class_name": "CodingAgent" }], + }, + "migrations": [{ "tag": "v1", "new_sqlite_classes": ["CodingAgent"] }], + "containers": [ + { "class_name": "CodingAgent", "scheduling_policy": "durable_object" }, + ], + "vars": { + "AI_GATEWAY_URL": "https://gateway.ai.cloudflare.com/v1//", + }, +} +``` + + + +Store your [AI Gateway](/ai-gateway/) token as a secret: + +```sh +npx wrangler secret put AI_GATEWAY_TOKEN +``` + +## Create the harness + +Pick what runs in the container with a preset, `claudeCode()` or `codex()`: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { routeAgentRequest } from "agents"; +import { ContainerHarness, claudeCode } from "agents/harness/container"; +import { Lifecycle } from "agents/lifecycle"; + +// Adds the credentials outside the container. Export it from the main module. +export { ContainerEgress } from "agents/harness/container"; + +export class CodingAgent extends DurableObject { + readonly harness = new ContainerHarness({ + container: this.ctx.container!, + egress: this.ctx.exports.ContainerEgress, + agent: claudeCode({ + baseUrl: `${this.env.AI_GATEWAY_URL}/anthropic`, + apiKey: this.env.AI_GATEWAY_TOKEN, + }), + }); + + readonly lifecycle = Lifecycle.install(this).use(this.harness); + + async onRequest(request: Request): Promise { + const { status, text } = await this.harness.prompt(await request.text()); + return Response.json({ status, text }); + } +} + +export default { + async fetch(request: Request, env: Env): Promise { + return ( + (await routeAgentRequest(request, env)) ?? + new Response("Not found", { status: 404 }) + ); + }, +} satisfies ExportedHandler; +``` + + + +To run Codex instead, replace `claudeCode()` with `codex()` and point `baseUrl` at `${this.env.AI_GATEWAY_URL}/openai`. Leave out `baseUrl` to call the provider directly with its own key, and add `headers` for anything else a request needs. + +The routes in this example have no authentication. Each request drives an agent that runs commands and spends model credit, so put your own authentication in front of it before you deploy. + +## How it works + +- **First start**: the harness starts `cloudflare/debian-trixie`, creates an unprivileged `agent` user, installs the CLI, and snapshots the filesystem. Later starts restore the snapshot. Changing the preset, such as a new CLI version or extra `setup` steps, sets the container up again. +- **Each turn** is one CLI process. The harness turns its output into messages and events, and copies the CLI's session files to the Durable Object as the turn runs. +- **Credentials**: the CLI only sees a placeholder key and a placeholder host. The harness intercepts that host, and `ContainerEgress`, running in your Worker, adds the real credentials and forwards the request to `baseUrl`. +- **Idle and restart**: five minutes after the last prompt, the harness snapshots the container and stops it. The next prompt starts a new container from the snapshot, and the CLI resumes its own session. Call `harness.stop()` to stop the container sooner. +- **Lost containers**: if the container stops mid-run, that run settles as `unanswered` with the reason `container_lost`. Edits to the workspace since the last snapshot are lost. If the Durable Object is evicted mid-run, the container keeps working, and the object reattaches and replays what it missed. + +## Customize the CLI + +Pass `setup` steps to the preset to install plugins, skills, or settings. Steps run once, on the first start, and their result is kept in the snapshot. A step with `user: "agent"` runs as the user the CLI runs as: + +```ts +claudeCode({ + baseUrl: `${this.env.AI_GATEWAY_URL}/anthropic`, + apiKey: this.env.AI_GATEWAY_TOKEN, + setup: [ + { + name: "my plugins", + user: "agent", + command: [ + "sh", + "-c", + "claude plugin marketplace add you/my-plugins && claude plugin install my-plugin@my-plugins", + ], + }, + ], +}); +``` + +A token that a setup step uses stays in the snapshot, where the agent can read it. Prefer public sources or short-lived URLs. + +To run another CLI, build an image whose entrypoint serves the harness daemon with `cliAdapter` from `agents/harness/container/runtime`, and pass it to `containerAgent()`. + +## Related resources + +- [Harnesses](/agents/harnesses/) compares the harnesses you can run. +- [Scheduling policies](/containers/configuration/scheduling-policy/) describes the `durable_object` policy. +- [Example on GitHub](https://github.com/cloudflare/agents/tree/main/examples/next/harnesses/container) diff --git a/src/content/docs/agents/harnesses/index.mdx b/src/content/docs/agents/harnesses/index.mdx index a711b50bcc5..cd8be57dd94 100644 --- a/src/content/docs/agents/harnesses/index.mdx +++ b/src/content/docs/agents/harnesses/index.mdx @@ -1,77 +1,114 @@ --- title: Harnesses -description: Understand agent harnesses — the loop that controls planning, tool use, and response flow. +description: A harness is the agent loop around the model. Pick a harness, run it on a Durable Object with the Lifecycle, and plug in the capabilities it needs. pcx_content_type: concept sidebar: - order: 5 + order: 3 --- -import { LinkCard } from "~/components"; +import { LinkCard, TypeScriptExample } from "~/components"; -A harness is the loop that makes an agent behave like an agent instead of a single model call. +A harness is the loop that makes an agent behave like an agent instead of a single model call. On Cloudflare, the harness is the agent: you pick one, and it runs inside a [Durable Object](/durable-objects/). -It is responsible for the turn-by-turn work around the model: building the prompt, loading memory, selecting tools, handling tool results, streaming responses, persisting messages, and deciding whether the agent should continue or stop. +The harness does the turn-by-turn work around the model: building the prompt, loading memory, selecting tools, handling tool results, streaming responses, persisting messages, and deciding whether the agent should continue or stop. -To build a new agent, start with the [Pi harness](/agents/harnesses/pi/), which runs Pi Durable in a Durable Object. You can also run [OpenCode](/agents/harnesses/opencode/), or build the loop yourself on top of the [Agents SDK runtime](/agents/agent-class/). +## Run a harness on a Durable Object -## How harnesses fit +Running an agent on Cloudflare takes three steps: -Harnesses sit on top of the Agents SDK runtime: +1. **Pick a harness.** Install one of the [harnesses below](#choose-a-harness), such as the [Pi harness](/agents/harnesses/pi/), or [build your own](#build-your-own-harness). +2. **Run it on a Durable Object.** Install the harness with the [Lifecycle](/agents/lifecycle/). The Lifecycle runs startup, routes requests, and owns the object's job queue and alarm, so the harness wakes and carries on after eviction, deploys, and crashes. +3. **Plug in capabilities.** Add an [MCP client](/agents/capabilities/#mcp-client), a [scheduler](/agents/capabilities/#scheduler), [WebSockets](/agents/capabilities/#websockets), or any other [capability](/agents/capabilities/) the agent needs. Each one is a field and one more `.use()` call. -- **The runtime** gives the agent durable infrastructure: the [`Agent` class](/agents/agent-class/internals/), [state](/agents/agent-class/state/), [sessions](/agents/runtime/lifecycle/sessions/), [routing](/agents/agent-class/routing/), [WebSockets](/agents/agent-class/websockets/), [scheduling](/agents/agent-class/schedule-tasks/), [fibers](/agents/agent-class/durable-execution/), and [observability](/agents/runtime/operations/observability/). -- **The harness** gives the agent behavior: model calls, prompt construction, tool selection, stream handling, memory strategy, and lifecycle hooks. + -The runtime answers “where does this agent live and how does it stay durable?” The harness answers “what does this agent do on each turn?” +```ts +import { DurableObject } from "cloudflare:workers"; +import { PiHarness } from "agents/harness/pi"; +import { Lifecycle } from "agents/lifecycle"; +import { Scheduler } from "agents/schedules"; -## Choose an approach +export class Assistant extends DurableObject { + // The harness: the agent loop. + readonly harness = new PiHarness({ + /* model and tools */ + }); -Use Pi when you want [Pi Durable](https://earendil.com/posts/pi-durable/) to own the agent loop, transcript, and recovery. This is the recommended starting point for a new agent. `PiHarness` runs Pi in a Durable Object and wakes it after eviction. You choose the transport. + // A capability plugged in next to it. + readonly scheduler = new Scheduler({ callbacks: {} }); -Use a build-your-own harness when you need full control over the model call, message format, tool loop, or UI protocol. This is the right approach when you want to compose low-level APIs directly from the Agents SDK. + // The Lifecycle runs both on the Durable Object. + readonly lifecycle = Lifecycle.install(this) + .use(this.harness) + .use(this.scheduler); +} +``` -Use OpenCode (experimental) when you want OpenCode 2.0's sessions, tools, and plugin system. `OpenCodeHarness` runs [OpenCode's Workerd SDK](https://opencode.ai/v2/docs/build/sdk/cloudflare/) inside a plain Durable Object. + -## Current harnesses +For a complete, runnable agent, refer to the [Quick start](/agents/getting-started/quick-start/). - +## Choose a harness - +Except for Project Think, every harness below is a capability: it installs on a plain Durable Object with `Lifecycle.use()`, next to any other capability, and keeps many sessions per object through `sessions` and `session(id)`. Each session takes durable, idempotent submissions that you can wait on or abort. + +| Harness | Import | Status | What runs the loop | +| ----------------------------------------------------- | -------------------------- | ------------ | ------------------------------------------------------------------------ | +| [Pi](/agents/harnesses/pi/) | `agents/harness/pi` | Beta | [Pi Durable](https://earendil.com/posts/pi-durable/) | +| [AI SDK harness](/agents/harnesses/ai-sdk/) | `agents/harness/ai-sdk` | Experimental | The AI SDK's `streamText` | +| [Container harness](/agents/harnesses/container/) | `agents/harness/container` | Experimental | Claude Code, Codex, or another agent CLI in a Cloudflare Container | +| [OpenCode](/agents/harnesses/opencode/) | `agents/harness/opencode` | Experimental | [OpenCode](https://opencode.ai/) 2.0, inside the Durable Object | +| [Project Think](/agents/harnesses/think/) | `@cloudflare/think` | Experimental | Think's agent loop, as a class that extends the `Agent` class | + +Use **Pi** when you want Pi Durable to own the agent loop, transcript, and recovery, including steering a running turn. You choose how clients reach it, such as HTTP, WebSockets, or RPC. Start here if you are not sure. + +Use the **AI SDK harness** when you want a thin `streamText` loop with sessions and a queue of operations, and nothing more. + +Use the **Container harness** when you want a coding agent CLI with a real filesystem and shell. The CLI runs in a container that stops when idle, while the Durable Object keeps the sessions. + +Use **OpenCode** when you want OpenCode 2.0's sessions, tools, and plugin system, running inside the Durable Object itself. + +Use **Project Think** when you want every default built in: memory, workspace tools, actions, extensions, messengers, scheduled tasks, and sub-agent RPC. Think is a class: your agent extends `Think`, which builds on the legacy [`Agent` class](/agents/agent-class/). -## What a harness usually owns +## Build your own harness + +When you need full control over the model call, message format, tool loop, or UI protocol, write your own harness as a [capability](/agents/lifecycle/build-a-capability/): + +- Combine [Sessions, Streams, and Tasks](/agents/capabilities/#build-your-own-harness) to store the conversation, stream output, and run turns as replayable work. +- If your agent runs somewhere other than the Durable Object, such as a container or a remote process, keep its durable record in `HarnessStore` from `agents/harness/store` (experimental). It stores sessions, operations with idempotency keys, and named logs of JSON entries per session, in whatever format your agent speaks. The Container harness uses it. + +```ts +import { openHarnessStore } from "agents/harness/store"; + +const store = openHarnessStore(this.ctx.storage, { prefix: "my_harness_" }); +store.createSession({ id: "main" }); +store.enqueue({ session: "main", id: operationId, input: { text } }); +store.append("main", "messages", [{ data: message }]); +``` + +## What a harness owns A harness usually owns: -- **Prompt construction** — system prompts, memory, retrieved context, and per-turn instructions. -- **Model execution** — the call to Workers AI, OpenAI, Anthropic, Gemini, or another provider. -- **Tool orchestration** — server tools, client tools, MCP tools, approval flows, and continuation after tool results. -- **Message persistence** — how user, assistant, and tool messages are saved and replayed. -- **Streaming and recovery** — how responses stream to clients and resume after disconnects or Durable Object eviction. -- **Extension points** — hooks before and after turns, steps, tool calls, and recovery events. +- **Prompt construction**: system prompts, memory, retrieved context, and per-turn instructions. +- **Model execution**: the call to Workers AI, OpenAI, Anthropic, Gemini, or another provider. For model setup, refer to [Models](/agents/models/). +- **Tool orchestration**: server tools, client tools, MCP tools, approval flows, and continuation after tool results. +- **Message persistence**: how user, assistant, and tool messages are saved and replayed. +- **Streaming and recovery**: how responses stream to clients and resume after disconnects or Durable Object eviction. +- **Extension points**: hooks before and after turns, steps, tool calls, and recovery events. -## Related resources +The harness does not own where the agent lives or how it wakes up. The Durable Object and the Lifecycle handle that, and capabilities add the rest. - +## Related resources diff --git a/src/content/docs/agents/harnesses/opencode.mdx b/src/content/docs/agents/harnesses/opencode.mdx index d5f84525fde..a92fc18b319 100644 --- a/src/content/docs/agents/harnesses/opencode.mdx +++ b/src/content/docs/agents/harnesses/opencode.mdx @@ -3,7 +3,8 @@ title: OpenCode description: Run OpenCode 2.0 in a plain Durable Object with the Agents SDK, Workers AI and AI Gateway models, bundled plugins, and durable sessions. pcx_content_type: overview sidebar: - order: 10 + order: 5 + badge: Experimental products: - agents reviewed: 2026-10-12 @@ -29,7 +30,7 @@ OpenCode owns the agent loop, transcript, tools, and session recovery. `OpenCode ## How it works -`OpenCodeHarness` is a [Lifecycle capability](https://github.com/cloudflare/agents/blob/main/docs/agents/lifecycle.md). The Lifecycle starts the capability and provides storage and a durable job queue. +`OpenCodeHarness` is a [Lifecycle capability](/agents/lifecycle/build-a-capability/). The Lifecycle starts the capability and provides storage and a durable job queue. ```mermaid flowchart LR @@ -339,4 +340,4 @@ export default Plugin.define({ -Import this plugin and add it to `plugins`. The `content` supplies instructions without reading a local file at runtime. Refer to [OpenCode skills](https://opencode.ai/v2/docs/build/plugins/#skills) for registration and [Agent Skills](/agents/runtime/execution/agent-skills/) for authoring guidance. +Import this plugin and add it to `plugins`. The `content` supplies instructions without reading a local file at runtime. Refer to [OpenCode skills](https://opencode.ai/v2/docs/build/plugins/#skills) for registration and [Agent Skills](/agents/tools/agent-skills/) for authoring guidance. diff --git a/src/content/docs/agents/harnesses/pi/extensions.mdx b/src/content/docs/agents/harnesses/pi/extensions.mdx index c5bda34f4e8..45f7327d2bf 100644 --- a/src/content/docs/agents/harnesses/pi/extensions.mdx +++ b/src/content/docs/agents/harnesses/pi/extensions.mdx @@ -129,7 +129,7 @@ this.registry.install({ ## Add skills -`skills(sources)` from `agents/harness/pi` turns [Agent Skills](/agents/runtime/execution/agent-skills/) sources from `agents/skills` into a Pi extension named `agents.skills`. It has two tools, `activate_skill` and `read_skill_resource`, and a `skills` section that lists the skills. It reads the sources, so install it in an `async` factory: +`skills(sources)` from `agents/harness/pi` turns [Agent Skills](/agents/tools/agent-skills/) sources from `agents/skills` into a Pi extension named `agents.skills`. It has two tools, `activate_skill` and `read_skill_resource`, and a `skills` section that lists the skills. It reads the sources, so install it in an `async` factory: ```ts import { skills } from "agents/harness/pi"; diff --git a/src/content/docs/agents/harnesses/pi/index.mdx b/src/content/docs/agents/harnesses/pi/index.mdx index cb592077ce7..b70452f0541 100644 --- a/src/content/docs/agents/harnesses/pi/index.mdx +++ b/src/content/docs/agents/harnesses/pi/index.mdx @@ -5,7 +5,7 @@ description: Run the Pi Durable harness on Cloudflare with the Agents SDK. PiHar tags: - AI sidebar: - order: 9 + order: 1 group: hideIndex: false products: diff --git a/src/content/docs/agents/harnesses/think/configuration.mdx b/src/content/docs/agents/harnesses/think/configuration.mdx index f6e15ba066c..20e484c8cf4 100644 --- a/src/content/docs/agents/harnesses/think/configuration.mdx +++ b/src/content/docs/agents/harnesses/think/configuration.mdx @@ -24,8 +24,8 @@ Think is configured by overriding methods and properties on your `Think` subclas | `getMessengers()` | `{}` | Messenger ingress and delivery declarations — refer to [Messengers](/agents/harnesses/think/messengers/) | | `maxSteps` | `10` | Max tool-call rounds per turn | | `sendReasoning` | `true` | Send reasoning chunks to chat clients | -| `configureSession()` | identity | Add context blocks, compaction, search, skills — refer to [Sessions](/agents/runtime/lifecycle/sessions/) | -| `getSkills()` | `[]` | Return Agent Skills sources for on-demand skill activation — refer to [Agent Skills](/agents/runtime/execution/agent-skills/) | +| `configureSession()` | identity | Add context blocks, compaction, search, skills — refer to [Sessions](/agents/capabilities/sessions/) | +| `getSkills()` | `[]` | Return Agent Skills sources for on-demand skill activation — refer to [Agent Skills](/agents/tools/agent-skills/) | | `getSkillScriptRunner()` | `null` | Enable the optional `run_skill_script` tool | | `messageConcurrency` | `"queue"` | How overlapping submits behave — refer to [Client tools](/agents/harnesses/think/client-tools/#message-concurrency) | | `includeMcpTools` | `true` | Convert connected MCP tools to AI SDK tools and add them to model turns. Refer to [MCP tools](/agents/harnesses/think/tools/#mcp-tools) | @@ -87,7 +87,7 @@ export class MyAgent extends Think { ## Session integration -Think stores conversations in a [Session](/agents/runtime/lifecycle/sessions/) — the storage layer that holds your messages and gives the model writable memory. Two concepts come up here: **context blocks** are labelled sections of the system prompt the model can read and update (for example, a `memory` block of facts about the user), and **compaction** summarizes older messages so long conversations stay within the model's context window. Override `configureSession` to add persistent memory, compaction, search, and skills: +Think stores conversations in a [Session](/agents/capabilities/sessions/) — the storage layer that holds your messages and gives the model writable memory. Two concepts come up here: **context blocks** are labelled sections of the system prompt the model can read and update (for example, a `memory` block of facts about the user), and **compaction** summarizes older messages so long conversations stay within the model's context window. Override `configureSession` to add persistent memory, compaction, search, and skills: @@ -117,7 +117,7 @@ export class MyAgent extends Think { When `configureSession` adds context blocks, Think builds the system prompt from those blocks instead of using `getSystemPrompt()`. Think's `this.messages` getter reads directly from Session's tree-structured storage. -For the full Session API — context blocks, compaction, search, skills, and multi-session support — refer to the [Sessions documentation](/agents/runtime/lifecycle/sessions/). +For the full Session API — context blocks, compaction, search, skills, and multi-session support — refer to the [Sessions documentation](/agents/capabilities/sessions/). ## Package exports @@ -150,4 +150,4 @@ Bundled with `@cloudflare/think`: | ---------------------- | ---------------------------------------- | | `@cloudflare/codemode` | Code execution for `createExecuteTool()` | -The Agent Skills engine and its script runner live in [`agents/skills`](/agents/runtime/execution/agent-skills/), so skill scripts pull `@cloudflare/worker-bundler` and `just-bash` through `agents`, not Think. +The Agent Skills engine and its script runner live in [`agents/skills`](/agents/tools/agent-skills/), so skill scripts pull `@cloudflare/worker-bundler` and `just-bash` through `agents`, not Think. diff --git a/src/content/docs/agents/harnesses/think/getting-started.mdx b/src/content/docs/agents/harnesses/think/getting-started.mdx index 065acce96db..8bf3453a9b7 100644 --- a/src/content/docs/agents/harnesses/think/getting-started.mdx +++ b/src/content/docs/agents/harnesses/think/getting-started.mdx @@ -254,7 +254,7 @@ Now the model sees a `MEMORY` section in its system prompt and gets a `set_conte When you use `configureSession`, the system prompt is built from context blocks rather than `getSystemPrompt()`. The `"soul"` block above acts as the system identity — it is read-only and always appears first. The `"memory"` block is writable, and the model proactively updates it when it learns something useful. -Refer to the [Sessions documentation](/agents/runtime/lifecycle/sessions/) for context blocks, compaction, search, skills, and multi-session support. +Refer to the [Sessions documentation](/agents/capabilities/sessions/) for context blocks, compaction, search, skills, and multi-session support. ## 7. Add custom tools @@ -345,4 +345,4 @@ Refer to [Lifecycle hooks](/agents/harnesses/think/lifecycle-hooks/) for the ful - [Tools](/agents/harnesses/think/tools/) — workspace tools, code execution, extensions - [Client tools](/agents/harnesses/think/client-tools/) — browser-side tools, approval flows, concurrency - [Sub-agent RPC and programmatic turns](/agents/harnesses/think/sub-agents/) — RPC streaming, scheduled turns, recovery -- [Sessions](/agents/runtime/lifecycle/sessions/) — context blocks, compaction, search, multi-session +- [Sessions](/agents/capabilities/sessions/) — context blocks, compaction, search, multi-session diff --git a/src/content/docs/agents/harnesses/think/index.mdx b/src/content/docs/agents/harnesses/think/index.mdx index d8edd145de3..4c8d4929d78 100644 --- a/src/content/docs/agents/harnesses/think/index.mdx +++ b/src/content/docs/agents/harnesses/think/index.mdx @@ -5,7 +5,7 @@ description: Opinionated chat agent framework with built-in tools, persistent me tags: - AI sidebar: - order: 11 + order: 6 group: hideIndex: false products: @@ -24,6 +24,8 @@ import { Think works as both a **top-level agent** (WebSocket chat to browser clients via `useAgentChat`) and a **sub-agent** (a child agent that another agent drives over RPC via `chat()`). +The `Think` class extends the legacy [`Agent` class](/agents/agent-class/). + :::note[New to Cloudflare Agents?] If this is your first agent, start with the [Getting started tutorial](/agents/harnesses/think/getting-started/) for a guided build. For the bigger picture of what agents are and how they run, read [What are agents?](/agents/concepts/what-are-agents/). Think builds on two Cloudflare primitives worth a quick look: [Workers AI](/workers-ai/) provides the model, and each agent instance is a [Durable Object](/durable-objects/) that stores its state. The rest of this section is reference material you can dip into as you need it. ::: @@ -147,7 +149,7 @@ enabled = true Traces appear in the Agents view in the Cloudflare Dashboard. You can inspect conversations and trace timelines. To turn tracing off, set `observability.traces.enabled` to `false`. You do not need to change the Think agent class. -For span attributes, payload controls, exporting traces, and direct AI SDK v6 or v7 setup, refer to [Tracing](/agents/runtime/operations/observability/tracing/). +For span attributes, payload controls, exporting traces, and direct AI SDK v6 or v7 setup, refer to [Tracing](/agents/platform/observability/tracing/). ## Think vs AIChatAgent @@ -374,7 +376,7 @@ Use [`startFiber()`](/agents/agent-class/durable-execution/#startfiber) outside /> @@ -393,7 +395,7 @@ Think's design is inspired by [Pi](https://pi.dev). ## Related -- [Sessions](/agents/runtime/lifecycle/sessions/) — context blocks, compaction, search, multi-session (the storage layer Think builds on) +- [Sessions](/agents/capabilities/sessions/) — context blocks, compaction, search, multi-session (the storage layer Think builds on) - [Sub-agents](/agents/agent-class/sub-agents/) — `subAgent()`, `abortSubAgent()`, `deleteSubAgent()` (the base Agent methods for spawning children) - [Chat agents](/agents/agent-class/communication-channels/chat/chat-agents/) — `AIChatAgent` for when you need full control over the LLM call - [Long-running agents](/agents/concepts/agentic-patterns/long-running-agents/) — sub-agent delegation patterns for multi-week agent lifetimes diff --git a/src/content/docs/agents/harnesses/think/recovery.mdx b/src/content/docs/agents/harnesses/think/recovery.mdx index a4f034e5477..366f73c0f7f 100644 --- a/src/content/docs/agents/harnesses/think/recovery.mdx +++ b/src/content/docs/agents/harnesses/think/recovery.mdx @@ -45,7 +45,7 @@ export class MyAgent extends Think { -The same recovery events are available through `agents/observability` on the `chat` channel; transcript repairs are emitted on the `transcript` channel. Refer to [Diagnostics channels](/agents/runtime/operations/observability/diagnostics-channels/#chat-recovery-events). +The same recovery events are available through `agents/observability` on the `chat` channel; transcript repairs are emitted on the `transcript` channel. Refer to [Diagnostics channels](/agents/platform/observability/diagnostics-channels/#chat-recovery-events). ## onChatRecovery @@ -194,7 +194,7 @@ This runs during transcript repair — before the repaired transcript is persist ## Context-window overflow recovery -[Compaction](/agents/runtime/lifecycle/sessions/#compaction) is checked **between turns** — `compactAfter()` runs after each `appendMessage()`. But a single long, tool-heavy turn grows the prompt step by step inside one `streamText` loop and can exceed the model context window **mid-turn**, before the next pre-turn check. The provider then rejects the request (`"prompt is too long"`, `context_length_exceeded`), and the turn would otherwise die terminally. +[Compaction](/agents/capabilities/sessions/#compaction) is checked **between turns** — `compactAfter()` runs after each `appendMessage()`. But a single long, tool-heavy turn grows the prompt step by step inside one `streamText` loop and can exceed the model context window **mid-turn**, before the next pre-turn check. The provider then rejects the request (`"prompt is too long"`, `context_length_exceeded`), and the turn would otherwise die terminally. Think recovers from this with two opt-in, provider-agnostic layers, both configured through the `contextOverflow` property. Both are off by default, so existing behavior is unchanged. Both reuse your session's compaction function, so they require a `configureSession()` with `onCompaction()` configured. Both require [`classifyChatError`](/agents/harnesses/think/lifecycle-hooks/#classifychaterror) to tell Think which errors are overflows — Think ships no provider-specific matching in core. @@ -236,10 +236,10 @@ export class MyAgent extends Think {
-Use either layer alone, or both together: the proactive guard avoids most overflows, and the reactive backstop catches any that still slip through (for example, a turn that starts already over budget, or a single tool result so large that compaction cannot help — in which case it terminalizes cleanly). Both apply to every turn entry path (WebSocket, sub-agent `chat()`, and programmatic `saveMessages()` / `submitMessages()`), and both emit a `chat:context:compacted` [diagnostics channel event](/agents/runtime/operations/observability/diagnostics-channels/#chat-context-events). +Use either layer alone, or both together: the proactive guard avoids most overflows, and the reactive backstop catches any that still slip through (for example, a turn that starts already over budget, or a single tool result so large that compaction cannot help — in which case it terminalizes cleanly). Both apply to every turn entry path (WebSocket, sub-agent `chat()`, and programmatic `saveMessages()` / `submitMessages()`), and both emit a `chat:context:compacted` [diagnostics channel event](/agents/platform/observability/diagnostics-channels/#chat-context-events). :::note -A no-op compaction cannot rescue an over-budget turn, so recovery is only as effective as your compaction configuration. For tool-heavy histories, configure a `tokenCounter` on `compactAfter()` (refer to [Sessions](/agents/runtime/lifecycle/sessions/#auto-compaction)). +A no-op compaction cannot rescue an over-budget turn, so recovery is only as effective as your compaction configuration. For tool-heavy histories, configure a `tokenCounter` on `compactAfter()` (refer to [Sessions](/agents/capabilities/sessions/#auto-compaction)). ::: For a runnable demo against a real Workers AI model, refer to the [`context-overflow-recovery` example](https://github.com/cloudflare/agents/tree/main/examples/context-overflow-recovery). diff --git a/src/content/docs/agents/harnesses/think/tools.mdx b/src/content/docs/agents/harnesses/think/tools.mdx index 5328b49d3f6..1a7b26b04ea 100644 --- a/src/content/docs/agents/harnesses/think/tools.mdx +++ b/src/content/docs/agents/harnesses/think/tools.mdx @@ -20,7 +20,7 @@ On every turn, Think merges tools from multiple sources. Later sources override 2. **`getTools()`** — your custom server-side tools 3. **Extension tools** — tools from loaded extensions (prefixed by extension name) 4. **Session tools** — `set_context`, `load_context`, `search_context` (from `configureSession`) -5. **Skill tools** — `activate_skill`, `read_skill_resource`, `run_skill_script` (from `getSkills()`, refer to [Agent Skills](/agents/runtime/execution/agent-skills/)) +5. **Skill tools** — `activate_skill`, `read_skill_resource`, `run_skill_script` (from `getSkills()`, refer to [Agent Skills](/agents/tools/agent-skills/)) 6. **MCP tools** — from connected MCP servers when `includeMcpTools` is `true` 7. **Client tools** — from the browser (refer to [Client tools](/agents/harnesses/think/client-tools/)) diff --git a/src/content/docs/agents/index.mdx b/src/content/docs/agents/index.mdx index 5c127c3d098..87d4e77f1e2 100644 --- a/src/content/docs/agents/index.mdx +++ b/src/content/docs/agents/index.mdx @@ -1,6 +1,6 @@ --- title: Build Agents on Cloudflare -description: Build and host AI agents on Cloudflare. Start with the Pi harness, which runs a durable model loop with tools in a Durable Object, and add state, real-time connections, and scheduled tasks with the Agents SDK. +description: Run an AI agent on a Cloudflare Durable Object. Start with the Pi harness, let the Lifecycle keep it running, and plug in capabilities for MCP, scheduling, and real-time clients. pcx_content_type: overview tags: - AI @@ -11,31 +11,62 @@ head: content: Agents --- -import { PackageManagers, RelatedProduct } from "~/components"; +import { PackageManagers, RelatedProduct, TypeScriptExample } from "~/components"; import AgentsPlatformDiagram from "~/components/cf/AgentsPlatformDiagram.astro"; -Build and host Agents on Cloudflare, connect chat, voice, email, Slack, and webhooks to a durable agent runtime with Browser, Sandbox, AI Search, MCP, Payments, and other MCP tools. +Run your agent on Cloudflare. An agent is a [harness](/agents/harnesses/), the loop around the model, running inside a [Durable Object](/durable-objects/): an addressable instance with its own SQLite database, alarms, and WebSocket connections. You can run one agent per user, conversation, or task, and an idle agent costs close to nothing. -When you host agents on Cloudflare, each agent session has a durable identity, local SQL storage, real-time connections, scheduled work, and recoverable execution. +To run an agent, pick a harness and install it on a Durable Object with the [Lifecycle](/agents/lifecycle/). Then plug in the [capabilities](/agents/capabilities/) your agent needs: -Deploy once and Cloudflare runs your agents across its global network, scaling to tens of millions of instances. No infrastructure to manage, no sessions to reconstruct, no state to externalize. + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { PiHarness } from "agents/harness/pi"; +import { Lifecycle } from "agents/lifecycle"; +import { MCPClientManager } from "agents/mcp/client"; +import { Scheduler } from "agents/schedules"; + +export class Assistant extends DurableObject { + readonly harness = new PiHarness({ + /* model and tools */ + }); + readonly mcp = new MCPClientManager("assistant", "1.0.0", { env: this.env }); + readonly scheduler = new Scheduler({ callbacks: {} }); + + readonly lifecycle = Lifecycle.install(this) + .use(this.harness) + .use(this.mcp) + .use(this.scheduler); +} +``` + + + +Deploy once, and Cloudflare runs your agents across its global network, scaling to tens of millions of instances. -Agents on Cloudflare are composed from four parts: +An agent on Cloudflare has these parts: + +- **A harness** is the agent. It runs the loop around the model: prompts, tool calls, retries, and recovery. Start with the [Pi harness](/agents/harnesses/pi/), or one of the experimental harnesses: the [AI SDK harness](/agents/harnesses/ai-sdk/), [OpenCode](/agents/harnesses/opencode/), or the [Container harness](/agents/harnesses/container/), which runs Claude Code or Codex. You can also extend [Project Think](/agents/harnesses/think/), or [build your own](/agents/harnesses/#build-your-own-harness). +- **A Durable Object** is where the agent lives. Each instance has its own storage and address, so each user or task gets its own agent. +- **The Lifecycle** keeps the harness running on the Durable Object. It runs startup, routes requests, and owns the object's durable job queue and its single alarm, so work continues across eviction, deploys, and crashes. +- **Capabilities** plug in what else the agent needs: an [MCP client](/agents/capabilities/#mcp-client), [schedules](/agents/capabilities/#scheduler), [queues](/agents/capabilities/#queue), [WebSockets](/agents/capabilities/#websockets) and [state](/agents/capabilities/#state) for clients, and [routed agents](/agents/capabilities/#routed-agents). +- **Tools** let the model act: [browser automation](/agents/tools/browser/), [sandboxed code execution](/agents/tools/sandbox/), [AI Search](/agents/tools/ai-search/), [MCP tools](/agents/tools/mcp/), and [payments](/agents/tools/payments/). [Code Mode](/agents/tools/codemode/) lets models orchestrate tools by writing code. -- **Communication channels** define how users and systems reach your agent, such as [chat](/agents/agent-class/communication-channels/chat/), [voice](/agents/agent-class/communication-channels/voice/), [email](/agents/agent-class/communication-channels/email/), [Slack](/agents/agent-class/communication-channels/slack/), [webhooks](/agents/agent-class/communication-channels/webhooks/), and other event sources. -- **The agent harness** defines the loop: how the agent calls models, selects tools, handles tool results, streams responses, and decides whether to continue. Use the [Pi harness](/agents/harnesses/pi/) to run Pi Durable in a Durable Object, or build your own loop directly on the [Agents SDK runtime](/agents/agent-class/). -- **The Agents SDK runtime** provides durable infrastructure: the [`Agent` class](/agents/agent-class/internals/), [state](/agents/agent-class/state/), [sessions](/agents/runtime/lifecycle/sessions/), [routing](/agents/agent-class/routing/), [WebSockets](/agents/agent-class/websockets/), [scheduling](/agents/agent-class/schedule-tasks/), [fibers](/agents/agent-class/durable-execution/), and [observability](/agents/runtime/operations/observability/). -- **Tools** give the agent capabilities: [browser automation](/agents/tools/browser/), [sandboxed code execution](/agents/tools/sandbox/), [AI Search](/agents/tools/ai-search/), [MCP tools](/agents/tools/mcp/), and [payments](/agents/tools/payments/). [Code Mode](/agents/tools/codemode/) lets models discover and orchestrate multiple tools by writing code. +Before harnesses, you built an agent by extending the [`Agent` class](/agents/agent-class/). The `Agent` class is still supported. It is a Durable Object with the Lifecycle and a fixed set of capabilities already installed, and it is the base for `@callable()` methods, [chat](/agents/agent-class/communication-channels/chat/), [voice](/agents/agent-class/communication-channels/voice/), [email](/agents/agent-class/communication-channels/email/), and other [communication channels](/agents/agent-class/communication-channels/). For a new agent, start with a harness. ### Get started -Build your agent with the [Pi harness](/agents/harnesses/pi/). [Pi Durable](https://earendil.com/posts/pi-durable/) runs the agent loop, including the transcript, model calls, tools, and retries, inside a Durable Object. `PiHarness` from the Agents SDK stores Pi's state in the object's SQLite database and resumes the run after an eviction. No API keys are required. The examples use [Workers AI](/workers-ai/) through the `AI` binding. +Build your agent with the [Pi harness](/agents/harnesses/pi/). [Pi Durable](https://earendil.com/posts/pi-durable/) runs the agent loop, including the transcript, model calls, tools, and retries, inside a Durable Object. `PiHarness` stores Pi's state in the object's SQLite database and resumes the run after an eviction. No API keys are required, because the examples use [Workers AI](/workers-ai/) through the `AI` binding. -The [Pi harness guide](/agents/harnesses/pi/) builds a first agent in one file. To start from a complete app with a browser UI, a durable filesystem, and code execution, deploy the Pi harness example: +- [Quick start](/agents/getting-started/quick-start/) builds the agent step by step. +- [Add to an existing project](/agents/getting-started/add-to-existing-project/) adds an agent to a Worker you already have. + +You can also deploy the Pi harness example, which streams sessions to a browser over WebSockets: [![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/agents/tree/main/examples/next/harnesses/pi) @@ -47,6 +78,8 @@ Build an agent on the Pi harness, with tools, sessions, and recovery after evict +The remaining examples build on the legacy `Agent` class. + Build a streaming AI chat agent with tools and human-in-the-loop approvals. diff --git a/src/content/docs/agents/lifecycle/build-a-capability.mdx b/src/content/docs/agents/lifecycle/build-a-capability.mdx new file mode 100644 index 00000000000..dba1d9c0ec9 --- /dev/null +++ b/src/content/docs/agents/lifecycle/build-a-capability.mdx @@ -0,0 +1,208 @@ +--- +title: Build a capability +description: Write your own reusable Lifecycle capability with LifecycleCapability, including storage, request middleware, durable jobs, events, and user callbacks. +pcx_content_type: how-to +sidebar: + order: 3 +products: + - agents +--- + +import { TypeScriptExample } from "~/components"; + +A capability is a class that the [Lifecycle](/agents/lifecycle/) starts and calls. It owns its own tables, its own jobs, and the routes it handles. Every capability in the Agents SDK, from `Scheduler` to `PiHarness`, is built this way, and you can write your own with the same API. + +This guide builds an `UptimeMonitor` capability. It checks a URL on an interval, stores each result, serves the history over HTTP, and calls back into the host when the URL is down. + +## Extend `LifecycleCapability` + +Extend `LifecycleCapability` and pass a stable, non-empty ID to `super()`. Lifecycle uses the ID to route jobs and events back to the capability, so do not change it after you deploy: + + + +```ts +import { LifecycleCapability } from "agents/lifecycle"; + +type UptimeMonitorOptions = { + url: string; + intervalMs: number; + onDown?: (status: number) => void | Promise; +}; + +class UptimeMonitor extends LifecycleCapability { + readonly options: UptimeMonitorOptions; + + constructor(options: UptimeMonitorOptions) { + super("uptime-monitor"); + this.options = options; + } +} +``` + + + +After you install the capability, `this.lifecycle` gives it these services: + +| Service | Description | +| --------------------------- | ----------------------------------------------------------------------------------- | +| `storage` | The Durable Object's storage, including `storage.sql` | +| `jobs` | The capability's own view of the [job queue](/agents/lifecycle/job-queue/) | +| `ready()` | Resolves once startup has finished, starting the object if needed | +| `status()` | `"zero"`, `"starting"`, or `"started"` | +| `name`, `className` | The object's name and its exported class name | +| `runInHostContext(fn)` | Runs a user callback inside the host's invocation context | +| `events.emit(type, data)` | Publishes a best-effort event to the `agents:*` diagnostics channels | +| `sockets`, `routes` | WebSocket access and routing between parent and child objects | + +The capability never receives the host object itself. Pass bindings and callbacks it needs through its constructor. + +## Create tables on startup + +Lifecycle calls `onStart()` on every capability before the host's `onStart()`. Use it to create or migrate the capability's tables. Prefix table names so they do not collide with the host's tables: + + + +```ts +class UptimeMonitor extends LifecycleCapability { + // ... + + override onStart(): void { + this.lifecycle.storage.sql.exec(` + CREATE TABLE IF NOT EXISTS uptime_checks ( + checked_at INTEGER NOT NULL, + status INTEGER NOT NULL + ) + `); + } +} +``` + + + +## Run work on a schedule + +Push a job to wake the capability later, and handle it in `onJob()`. Return `{ rescheduleAt }` to run it again: + + + +```ts +import { + LifecycleCapability, + type LifecycleJobContext, + type LifecycleJobOutcome, +} from "agents/lifecycle"; + +class UptimeMonitor extends LifecycleCapability { + // ... + + async startMonitoring(): Promise { + await this.lifecycle.ready(); + await this.lifecycle.jobs.push({ + id: "check", + fn: "check", + time: Date.now(), + }); + } + + async onJob({ job }: LifecycleJobContext): Promise { + if (job.fn !== "check") return; + const response = await fetch(this.options.url, { method: "HEAD" }); + this.lifecycle.storage.sql.exec( + "INSERT INTO uptime_checks (checked_at, status) VALUES (?, ?)", + Date.now(), + response.status, + ); + this.lifecycle.events.emit("uptime:check", { status: response.status }); + + const { onDown } = this.options; + if (!response.ok && onDown) { + await this.lifecycle.runInHostContext(() => onDown(response.status)); + } + return { rescheduleAt: Date.now() + this.options.intervalMs }; + } +} +``` + + + +The job has a fixed ID, so calling `startMonitoring()` again replaces the pending check instead of adding a second one. + +`startMonitoring()` awaits `this.lifecycle.ready()` first. A public method on a capability can run before startup, because [RPC calls](/agents/lifecycle/#call-your-agent-over-rpc) do not start the object the way requests and alarms do. Await `ready()` at the top of any method that uses storage, jobs, or work that `onStart()` also starts. `ready()` resolves immediately inside startup, so `onStart()` can call the same method. + +Capability hooks run outside the host's invocation context. Call user code through `runInHostContext()` so that [`getCurrentAgent()`](/agents/lifecycle/get-current-agent/) returns the host inside it. `Scheduler` and `Queue` call their registered callbacks the same way. + +## Handle requests + +Implement `onRequest()` to act as middleware in front of the host. Return a `Response` to handle the request, or `undefined` to pass it on: + + + +```ts +import { + LifecycleCapability, + type CapabilityRequestContext, +} from "agents/lifecycle"; + +class UptimeMonitor extends LifecycleCapability { + // ... + + onRequest({ request }: CapabilityRequestContext): Response | undefined { + if (!new URL(request.url).pathname.endsWith("/uptime")) return; + const checks = this.lifecycle.storage.sql + .exec( + "SELECT checked_at, status FROM uptime_checks ORDER BY checked_at DESC LIMIT 20", + ) + .toArray(); + return Response.json(checks); + } +} +``` + + + +Match only the routes the capability owns. A capability that handles every request should set `claims = "catch-all"`, so that Lifecycle runs it after all other capabilities. + +## Install the capability + +Install the capability like any other: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; + +export class SiteAgent extends DurableObject { + readonly uptime = new UptimeMonitor({ + url: "https://example.com/", + intervalMs: 5 * 60_000, + onDown: (status) => console.warn(`example.com returned ${status}`), + }); + readonly lifecycle = Lifecycle.install(this).use(this.uptime); + + async onRequest(): Promise { + await this.uptime.startMonitoring(); + return new Response("Monitoring started", { status: 202 }); + } +} +``` + + + +A request to `/agents/site-agent/example/uptime` returns the stored checks. Every other request reaches the host's `onRequest()`. + +The same capability works on the [`Agent` class](/agents/agent-class/). Install it with `this.lifecycle.use(this.uptime)` in the Agent's constructor. + +## Other hooks + +A capability can also implement these hooks: + +| Hook | Purpose | +| ------------------------------------------------------------- | -------------------------------------------------------------------- | +| `onWebSocketUpgrade(context)` | Claim a WebSocket upgrade by returning a `Response` | +| `onWebSocketMessage`, `onWebSocketClose`, `onWebSocketError` | Handle events for sockets the capability accepted | +| `onJobError(context, error)` | Decide what happens after a job fails its last retry | +| `onMemoryLimit(context)` | Apply policy after an alarm ends in a memory-limit reset | +| `dispose()` | Release live resources during explicit teardown | + +Events are best-effort telemetry, not a durable outbox. If delivery is part of your business logic, store an outbox in the capability's own table. diff --git a/src/content/docs/agents/lifecycle/get-current-agent.mdx b/src/content/docs/agents/lifecycle/get-current-agent.mdx new file mode 100644 index 00000000000..7178c9ce359 --- /dev/null +++ b/src/content/docs/agents/lifecycle/get-current-agent.mdx @@ -0,0 +1,76 @@ +--- +title: getCurrentAgent() +description: Read the current Durable Object, request, and WebSocket connection from shared code with getCurrentAgent() from agents/lifecycle. +pcx_content_type: reference +sidebar: + order: 4 +products: + - agents +--- + +import { TypeScriptExample } from "~/components"; + +`getCurrentAgent()` returns the Durable Object that is handling the current invocation, along with its request or WebSocket connection. Use it in shared code that does not have access to `this`, such as an AI SDK tool or a logging helper. + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { getCurrentAgent, Lifecycle } from "agents/lifecycle"; + +function requestOrigin(): string | undefined { + const { request } = getCurrentAgent(); + return request ? new URL(request.url).origin : undefined; +} + +export class MyAgent extends DurableObject { + readonly lifecycle = Lifecycle.install(this); + + onRequest(): Response { + return Response.json({ origin: requestOrigin() }); + } +} +``` + + + +Pass your class as a type parameter when shared code needs its methods: + +```ts +const { agent } = getCurrentAgent(); +``` + +## Return value + +| Property | Type | Description | +| ------------ | ------------------------- | ------------------------------------------------------------------ | +| `agent` | `T \| undefined` | The Durable Object handling the invocation | +| `connection` | `Connection \| undefined` | The WebSocket connection, inside `WebSockets` capability handlers | +| `request` | `Request \| undefined` | The HTTP request, inside `onRequest()` or a connect handler | +| `email` | `unknown` | The email, inside the `Agent` class email handler | + +Every value is `undefined` outside an invocation that Lifecycle started. + +## Context availability + +Lifecycle sets the context while it calls host hooks and user callbacks: + +| Invocation | `agent` | `connection` | `request` | +| ------------------------------------------ | ------- | ------------ | --------- | +| `onStart()`, `onAlarm()`, `onJob()` | Yes | No | No | +| `onRequest()` | Yes | No | Yes | +| `WebSockets` `onConnect` handler | Yes | Yes | Yes | +| `WebSockets` `onMessage`, `onClose`, `onError` handlers | Yes | Yes | No | +| `Scheduler` and `Queue` callbacks | Yes | No | No | + +Capability hooks run outside the host context. A capability that calls user code wraps the call in `this.lifecycle.runInHostContext()`. For more information, refer to [Build a capability](/agents/lifecycle/build-a-capability/). + +`Scheduler` and `Queue` callbacks run from the alarm, in a fresh invocation. They do not see the request or connection that created the schedule or queue item. + +## Native RPC + +Lifecycle does not wrap native Durable Object RPC methods, so `getCurrentAgent()` returns `undefined` values inside them. The [`Agent` class](/agents/agent-class/get-current-agent/) wraps its methods for you. + +## The `Agent` class + +`getCurrentAgent()` from `agents` is the same function, typed for the `Agent` class. It also returns the current email inside `onEmail()`. For more information, refer to [`getCurrentAgent()` on the Agent class](/agents/agent-class/get-current-agent/). diff --git a/src/content/docs/agents/lifecycle/index.mdx b/src/content/docs/agents/lifecycle/index.mdx new file mode 100644 index 00000000000..ce9e38df249 --- /dev/null +++ b/src/content/docs/agents/lifecycle/index.mdx @@ -0,0 +1,213 @@ +--- +title: Lifecycle +description: The Lifecycle keeps a harness and its capabilities running on a Durable Object. It runs startup, routes requests, and shares one durable job queue and alarm. +pcx_content_type: concept +sidebar: + order: 7 + group: + hideIndex: false +products: + - agents +--- + +import { PackageManagers, TypeScriptExample } from "~/components"; + +Every agent on Cloudflare is a [harness](/agents/harnesses/) running on a [Durable Object](/durable-objects/). `Lifecycle` from `agents/lifecycle` is what keeps it running there. It runs startup, offers each request to the harness and the other installed [capabilities](/agents/capabilities/), and owns the object's durable job queue and its single alarm. That is how a harness wakes and finishes its work after eviction, deploys, and crashes. + +`agents/lifecycle` is stable. You install the Lifecycle on a class that extends `DurableObject`. Most agents only need `Lifecycle.install(this)` and one `.use()` call for each capability. This section covers what the Lifecycle does underneath, and how to [build your own capability](/agents/lifecycle/build-a-capability/). The legacy [`Agent` class](/agents/agent-class/) installs the same Lifecycle for you. + +## Install the Lifecycle + + + +Construct the Lifecycle as a field on your Durable Object: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; + +export class MyAgent extends DurableObject { + readonly lifecycle = Lifecycle.install(this); + + onStart(): void { + // Runs once for each in-memory object lifetime, before any work. + } + + onRequest(request: Request): Response { + return new Response(`Hello from ${this.lifecycle.name}`); + } + + onAlarm(): void { + // Runs once for each alarm, after due jobs. + } +} +``` + + + +`Lifecycle.install(this)` installs the `fetch`, `alarm`, `webSocketMessage`, `webSocketClose`, and `webSocketError` handlers on your class. Do not define those methods yourself. Implement the hooks Lifecycle calls instead: + +| Hook | When it runs | +| -------------------- | ------------------------------------------------------------------------------- | +| `onStart(props)` | Once per in-memory lifetime, after every capability has started | +| `onRequest(request)` | For each HTTP request that no capability handled | +| `onAlarm()` | Once per alarm, after Lifecycle has run the due jobs | +| `onJob(context)` | When a job the host pushed through `lifecycle.jobs` comes due | + +The expanded form separates construction from handler installation: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; + +export class MyAgent extends DurableObject { + readonly lifecycle = new Lifecycle(this); + + constructor(ctx: DurableObjectState, env: Env) { + super(ctx, env); + this.lifecycle.installHandlers(); + } +} +``` + + + +Calling `installHandlers()` more than once throws an error. + +## Add capabilities + +A capability is a reusable piece of a Durable Object. It owns its own tables and behavior, and implements only the Lifecycle hooks it needs. Construct each capability as a field, then register it with `use()`: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { MCPClientManager } from "agents/mcp/client"; +import { Scheduler } from "agents/schedules"; + +export class MyAgent extends DurableObject { + readonly mcp = new MCPClientManager("my-agent", "1.0.0", { env: this.env }); + readonly scheduler = new Scheduler({ + callbacks: { + sendReminder: (payload: { message: string }) => { + console.log(payload.message); + }, + }, + }); + + readonly lifecycle = Lifecycle.install(this) + .use(this.mcp) + .use(this.scheduler); +} +``` + + + +Declare capability fields before the `lifecycle` field. Class fields initialize in order, so a capability declared after `lifecycle` is still `undefined` when `use()` runs. + +Register every capability before startup. `use()` throws after startup begins, or when a capability with the same identifier is already installed. For the list of capabilities, refer to [Capabilities](/agents/capabilities/). + +## Route requests to your agent + +Use `routeAgentRequest()` in the Worker's `fetch` handler to reach a named Durable Object: + + + +```ts +import { routeAgentRequest } from "agents"; + +export default { + async fetch(request: Request, env: Env): Promise { + return ( + (await routeAgentRequest(request, env)) ?? + new Response("Not found", { status: 404 }) + ); + }, +} satisfies ExportedHandler; +``` + + + +The default URL shape is `/agents/:binding/:name`, where `:binding` is the kebab-case Durable Object binding name. A request to `/agents/my-agent/alice` reaches the `MyAgent` instance named `alice`. + +You can also call the object directly with `env.MyAgent.getByName(name).fetch(request)`. + +### Use named objects + +Lifecycle reads the object's name from `ctx.id.name` and exposes it as `lifecycle.name`. Address objects with `getByName()` or `idFromName()`. Startup fails for an object addressed with an ID from `newUniqueId()` or `idFromString()`, because those IDs carry no name. + +## Startup and request handling + +Lifecycle starts on the first request, alarm, or WebSocket event of each in-memory lifetime: + +1. Each capability runs its `onStart()` hook, in registration order. +2. The host runs `onStart()`. +3. Lifecycle handles the event that woke the object. + +A warm object skips startup. If startup fails, the event fails and the next event tries again. + +For an HTTP request, Lifecycle offers the request to each capability in registration order. The first capability that returns a `Response` handles it. A capability that returns `undefined` passes the request on. A request that no capability handles reaches the host's `onRequest()`. + +```txt +routeAgentRequest(request) +└─ stub.fetch(request) + └─ Lifecycle fetch + ├─ startup (cold objects only) + ├─ capabilities, in registration order + │ └─ the first Response handles the request + └─ host onRequest() +``` + +A capability declares `claims: "catch-all"` when it handles everything offered to a hook. Lifecycle always runs catch-alls after every selective capability. The `WebSockets` capability is a catch-all for upgrades, so other capabilities can claim their own upgrade routes first. Without a capability that accepts upgrades, Lifecycle declines WebSocket upgrades. + +There is no `next()` callback. A capability either handles a request or declines it. + +## Call your agent over RPC + +Public methods on a Durable Object are [RPC methods](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/). RPC calls do not pass through `fetch`, so Lifecycle does not start the object for them. Call `lifecycle.start()` first in a method that needs started capabilities: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { Scheduler } from "agents/schedules"; + +export class MyAgent extends DurableObject { + readonly scheduler = new Scheduler({ callbacks: {} }); + readonly lifecycle = Lifecycle.install(this).use(this.scheduler); + + async pendingSchedules(): Promise { + await this.lifecycle.start(); + return (await this.scheduler.list()).length; + } +} +``` + + + +Concurrent calls to `start()` share one startup. `lifecycle.isStarted()` returns `true` once every capability and the host `onStart()` have finished. Many capability methods, such as `scheduler.set()` and `harness.prompt()`, wait for startup on their own. + +## Jobs and alarms + +A Durable Object has one alarm. Lifecycle owns it and schedules it from a durable queue of jobs, so capabilities such as `Scheduler`, `Queue`, `Tasks`, and `PiHarness` never overwrite each other's alarms. + +Do not call `ctx.storage.setAlarm()` on an object that uses Lifecycle. Push a job through `lifecycle.jobs` instead. For more information, refer to [Jobs and alarms](/agents/lifecycle/job-queue/). + +## Clean up + +`lifecycle.dispose()` calls each capability's `dispose()` hook in reverse registration order. Disposal releases live resources, such as MCP transports and listeners. It does not delete stored data. + +Eviction does not call `dispose()`. Call it during explicit teardown, before you delete the object's storage with `ctx.storage.deleteAll()`. + +## Related resources + +- [Capabilities](/agents/capabilities/) lists the capabilities you can install. +- [Jobs and alarms](/agents/lifecycle/job-queue/) describes the durable job queue. +- [Build a capability](/agents/lifecycle/build-a-capability/) shows how to write your own. +- [`getCurrentAgent()`](/agents/lifecycle/get-current-agent/) reads the current object, request, and connection. diff --git a/src/content/docs/agents/lifecycle/job-queue.mdx b/src/content/docs/agents/lifecycle/job-queue.mdx new file mode 100644 index 00000000000..d7d10b3c376 --- /dev/null +++ b/src/content/docs/agents/lifecycle/job-queue.mdx @@ -0,0 +1,126 @@ +--- +title: Jobs and alarms +description: Push durable jobs onto the Lifecycle job queue, handle them with onJob(), and control retries, rescheduling, and the single Durable Object alarm. +pcx_content_type: reference +sidebar: + order: 2 +products: + - agents +--- + +import { TypeScriptExample } from "~/components"; + +A Durable Object has one [alarm](/durable-objects/api/alarms/). The [Lifecycle](/agents/lifecycle/) owns that alarm and schedules it from a durable queue of jobs stored in the object's SQLite database. `Scheduler`, `Queue`, `Tasks`, and `PiHarness` all store their future work as jobs, so they share the alarm without overwriting each other. + +You can push jobs from the host Durable Object or from your own [capability](/agents/lifecycle/build-a-capability/). + +## Push a job from the host + +A job names a function, a due time in Unix milliseconds, and an optional JSON payload. Push it through `lifecycle.jobs` and handle it in `onJob()`: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { + Lifecycle, + type LifecycleJobContext, + type LifecycleJobOutcome, +} from "agents/lifecycle"; + +export class CleanupAgent extends DurableObject { + readonly lifecycle = Lifecycle.install(this); + + async onRequest(): Promise { + await this.lifecycle.jobs.push({ + id: "cleanup", + fn: "deleteExpired", + time: Date.now() + 60_000, + retry: { maxAttempts: 3 }, + }); + return new Response("Cleanup scheduled", { status: 202 }); + } + + async onJob({ job }: LifecycleJobContext): Promise { + if (job.fn !== "deleteExpired") return; + this.ctx.storage.sql.exec( + "DELETE FROM sessions WHERE expires_at < ?", + Date.now(), + ); + } +} +``` + + + +The job is durable once `push()` resolves. If the object is evicted, the alarm wakes it when the job is due. + +## Job options + +`push()` accepts these options: + +| Option | Description | +| -------------------- | --------------------------------------------------------------------------------------------------------------- | +| `fn` | Required. The function name your `onJob()` dispatches on. | +| `time` | Required. Due time in Unix milliseconds. | +| `payload` | Optional JSON-serializable value passed to `onJob()` as `job.payload`. | +| `id` | Stable job ID. Pushing an existing ID replaces that job. Lifecycle generates an ID when you omit it. | +| `retry` | Retry policy for failed dispatches: `maxAttempts`, `baseDelayMs`, and `maxDelayMs`. | +| `singleflight` | Skip the job while a previous run of it is still in progress. | +| `hungTimeoutSeconds` | Seconds before an in-progress single-flight run counts as hung. Defaults to `30`. | +| `exclusive` | Hold back other alarm work while this job is pending. The `Agent` class uses this for deferred destruction. | + +Job IDs are scoped to their owner. The host and each capability can use the same ID without conflict. A capability cannot replace or cancel a job that another owner pushed. + +`lifecycle.jobs` also has `cancel(id)`, `reschedule(id, time)`, `get(id)`, and `list()`. + +## Control what happens after a job runs + +The value `onJob()` returns decides what happens to the job: + +| Return value | Result | +| ------------------------ | ---------------------------------------------------------- | +| Nothing | The job completes and Lifecycle deletes it. | +| `{ rescheduleAt: time }` | The job stays in the queue until `time`. | +| `"yield"` | The job stays due, and the object wakes again immediately. | + +Return `{ rescheduleAt }` for recurring work, such as a poll every five minutes. Return `"yield"` to split a long job into smaller pieces. + +## Retries and failures + +When `onJob()` throws, Lifecycle retries the job in the same invocation, up to the job's `retry.maxAttempts`. Platform failures, such as a deploy that replaces the isolate or a memory-limit reset, keep the job for a fresh invocation instead of using up its attempts. + +A capability can implement `onJobError(context, error)` to decide what happens after the last attempt fails. A host job that fails every attempt completes. + +Before it runs any job, Lifecycle sets a backup alarm. If the isolate dies mid-run, that alarm wakes the object to resume the work. + +## How the alarm runs + +When the alarm fires, Lifecycle: + +1. Runs every due job in due-time order. +2. Runs the host's `onAlarm()`. +3. Sets the alarm for the next due job. + +Every change to the queue resets the alarm, so you never set it yourself. Do not call `ctx.storage.setAlarm()` on an object that uses Lifecycle, because that alarm would replace the one Lifecycle manages. + +Keep each `onJob()` call short. Lifecycle awaits jobs one at a time, so a slow job delays every other job on the object. For long work, start it, record its progress in storage, and return. [`Tasks`](/agents/capabilities/#tasks) does this for you with replayable steps. + +## Memory-limit protection + +A job that exhausts the isolate's memory can crash every alarm that retries it. Lifecycle counts consecutive alarm invocations that end in a memory-limit reset. Each one delays the next alarm with a growing backoff, up to five minutes. After three in a row, Lifecycle stops the retry loop and removes the jobs that capabilities marked as recovery loops. Set the limit with the `maxAlarmMemoryLimitStrikes` option: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; + +export class MyAgent extends DurableObject { + readonly lifecycle = Lifecycle.install(this, { + maxAlarmMemoryLimitStrikes: 5, + }); +} +``` + + diff --git a/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx b/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx index 4600cac1fbf..5c28166e880 100644 --- a/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx +++ b/src/content/docs/agents/model-context-protocol/apis/agent-api.mdx @@ -338,9 +338,9 @@ For a stateless server, read the same props with `getMcpAuthContext()`. Refer to The `McpAgent` class provides full access to the [Agent state APIs](/agents/agent-class/state/): - [`state`](/agents/agent-class/state/) — Current persisted state -- [`initialState`](/agents/agent-class/state/#set-the-initial-state-for-an-agent) — Default state when instance starts +- [`initialState`](/agents/agent-class/state/#defining-initial-state) — Default state when instance starts - [`setState`](/agents/agent-class/state/) — Update and persist state -- [`onStateChanged`](/agents/agent-class/state/#synchronizing-state) — React to state changes +- [`onStateChanged`](/agents/agent-class/state/#client-side-state-sync) — React to state changes - [`sql`](/agents/agent-class/#sql-api) — Execute SQL queries on embedded database :::note[State resets after the session ends] diff --git a/src/content/docs/agents/models/index.mdx b/src/content/docs/agents/models/index.mdx index 62e6bf1159d..46e982d9ea0 100644 --- a/src/content/docs/agents/models/index.mdx +++ b/src/content/docs/agents/models/index.mdx @@ -5,7 +5,7 @@ description: Call Workers AI, AI Gateway catalog models, dynamic routes, and ven tags: - AI sidebar: - order: 4.5 + order: 4 badge: Beta group: hideIndex: false diff --git a/src/content/docs/agents/runtime/operations/configuration.mdx b/src/content/docs/agents/platform/configuration.mdx similarity index 99% rename from src/content/docs/agents/runtime/operations/configuration.mdx rename to src/content/docs/agents/platform/configuration.mdx index ab9a592a4f1..25add28a335 100644 --- a/src/content/docs/agents/runtime/operations/configuration.mdx +++ b/src/content/docs/agents/platform/configuration.mdx @@ -3,7 +3,7 @@ title: Configuration description: Configure Wrangler bindings, environment variables, and type generation for a project using the Agents SDK. pcx_content_type: concept sidebar: - order: 3 + order: 2 products: - agents --- diff --git a/src/content/docs/agents/runtime/operations/cross-domain-authentication.mdx b/src/content/docs/agents/platform/cross-domain-authentication.mdx similarity index 99% rename from src/content/docs/agents/runtime/operations/cross-domain-authentication.mdx rename to src/content/docs/agents/platform/cross-domain-authentication.mdx index 75270b95c4f..7dd53a36574 100644 --- a/src/content/docs/agents/runtime/operations/cross-domain-authentication.mdx +++ b/src/content/docs/agents/platform/cross-domain-authentication.mdx @@ -3,7 +3,7 @@ title: Cross-domain authentication description: Authenticate WebSocket connections to Cloudflare Agents across domains using signed tokens. pcx_content_type: how-to sidebar: - order: 13 + order: 4 products: - agents --- diff --git a/src/content/docs/agents/platform/index.mdx b/src/content/docs/agents/platform/index.mdx index 4dd2f7adb1e..a1aa3cacdf7 100644 --- a/src/content/docs/agents/platform/index.mdx +++ b/src/content/docs/agents/platform/index.mdx @@ -1,9 +1,9 @@ --- pcx_content_type: reference title: Platform -description: Review platform details for Cloudflare Agents, including limits and configuration options. +description: Review platform details for Cloudflare Agents, including limits, configuration, observability, and authentication. sidebar: - order: 9 + order: 10 group: hideIndex: true products: diff --git a/src/content/docs/agents/runtime/operations/observability/diagnostics-channels.mdx b/src/content/docs/agents/platform/observability/diagnostics-channels.mdx similarity index 99% rename from src/content/docs/agents/runtime/operations/observability/diagnostics-channels.mdx rename to src/content/docs/agents/platform/observability/diagnostics-channels.mdx index 514d08fc8f2..45fdc05d387 100644 --- a/src/content/docs/agents/runtime/operations/observability/diagnostics-channels.mdx +++ b/src/content/docs/agents/platform/observability/diagnostics-channels.mdx @@ -299,13 +299,13 @@ These events track chat message lifecycle, client-side tool interactions, and Th diff --git a/src/content/docs/agents/runtime/operations/observability/index.mdx b/src/content/docs/agents/platform/observability/index.mdx similarity index 93% rename from src/content/docs/agents/runtime/operations/observability/index.mdx rename to src/content/docs/agents/platform/observability/index.mdx index 59aca38fbbb..57aef9f4766 100644 --- a/src/content/docs/agents/runtime/operations/observability/index.mdx +++ b/src/content/docs/agents/platform/observability/index.mdx @@ -2,7 +2,7 @@ title: Observability pcx_content_type: navigation sidebar: - order: 30 + order: 3 group: hideIndex: true --- diff --git a/src/content/docs/agents/runtime/operations/observability/tracing.mdx b/src/content/docs/agents/platform/observability/tracing.mdx similarity index 94% rename from src/content/docs/agents/runtime/operations/observability/tracing.mdx rename to src/content/docs/agents/platform/observability/tracing.mdx index 3ac85830243..298efa9a560 100644 --- a/src/content/docs/agents/runtime/operations/observability/tracing.mdx +++ b/src/content/docs/agents/platform/observability/tracing.mdx @@ -41,7 +41,7 @@ Select an agent to see its traces. Each trace includes its duration, token break There are two ways to follow what an agent did: **Session replay** and **Trace**. -**Session replay** shows the recorded conversation across turns, including messages, reasoning, tool calls, and subagent activity. What appears depends on your [payload recording settings](/agents/runtime/operations/observability/tracing/#payload-privacy). +**Session replay** shows the recorded conversation across turns, including messages, reasoning, tool calls, and subagent activity. What appears depends on your [payload recording settings](/agents/platform/observability/tracing/#payload-privacy). ![Session replay showing messages, reasoning, and subagent tool calls](~/assets/images/workers-observability/agent_tracing_session_replay.png) @@ -78,7 +78,7 @@ Do not derive the agent name from a request, conversation, or user identifier. T ### Payload privacy -Message and tool payloads can contain personally identifiable information. Only record payloads that are safe to store. Each integration controls payload recording differently, so configure it in the relevant [Framework setup](/agents/runtime/operations/observability/tracing/#framework-setup) section. +Message and tool payloads can contain personally identifiable information. Only record payloads that are safe to store. Each integration controls payload recording differently, so configure it in the relevant [Framework setup](/agents/platform/observability/tracing/#framework-setup) section. Think and `wrapAISDK()` use `storeMessages` to record input and output messages on `chat` spans. They use `storeTools` to record arguments and results on `execute_tool` spans. @@ -88,7 +88,7 @@ Tracing and payload controls depend on the integration. Think and Flue instrumen ### Think -Think automatically instruments your agent and emits the [standard span structure](/agents/runtime/operations/observability/tracing/#trace-structure). No additional tracing setup is required. +Think automatically instruments your agent and emits the [standard span structure](/agents/platform/observability/tracing/#trace-structure). No additional tracing setup is required. #### Store payloads @@ -109,7 +109,7 @@ export class MyAgent extends Think { ### Flue -[Flue v2+](https://flueframework.com/blog/flue-2/) also automatically instruments your agent and emits the [standard span structure](/agents/runtime/operations/observability/tracing/#trace-structure). No additional tracing setup is required. +[Flue v2+](https://flueframework.com/blog/flue-2/) also automatically instruments your agent and emits the [standard span structure](/agents/platform/observability/tracing/#trace-structure). No additional tracing setup is required. #### Exclude payloads @@ -143,7 +143,7 @@ const tracedAI = wrapAISDK(ai); `wrapAISDK()` supports AI SDK v6 and v7. It instruments `generateText`, `streamText`, `generateObject`, and `streamObject`, creating the `invoke_agent` parent before model and tool work begins. -Unlike Think, a direct AI SDK call has no Agent instance from which to infer dashboard identity. Supply the [agent identity fields](/agents/runtime/operations/observability/tracing/#agent-identity) on each call. +Unlike Think, a direct AI SDK call has no Agent instance from which to infer dashboard identity. Supply the [agent identity fields](/agents/platform/observability/tracing/#agent-identity) on each call. Only include context that is safe to store. Do not include credentials, tokens, user input, or other secrets. @@ -267,13 +267,13 @@ Beginning December 1, 2026, agent traces contribute to account-level ingestion a diff --git a/src/content/docs/agents/runtime/execution/index.mdx b/src/content/docs/agents/runtime/execution/index.mdx deleted file mode 100644 index 6014c2a070b..00000000000 --- a/src/content/docs/agents/runtime/execution/index.mdx +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: Execution -pcx_content_type: navigation -sidebar: - order: 3 - group: - hideIndex: true ---- - -import { DirectoryListing } from "~/components"; - - diff --git a/src/content/docs/agents/runtime/index.mdx b/src/content/docs/agents/runtime/index.mdx deleted file mode 100644 index af883fedb83..00000000000 --- a/src/content/docs/agents/runtime/index.mdx +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: Runtime -pcx_content_type: navigation -sidebar: - order: 6 - group: - hideIndex: true ---- - -import { DirectoryListing } from "~/components"; - -The runtime powers your agent — state, communication, execution, and operations. - - diff --git a/src/content/docs/agents/runtime/lifecycle/index.mdx b/src/content/docs/agents/runtime/lifecycle/index.mdx deleted file mode 100644 index cc12db0abd2..00000000000 --- a/src/content/docs/agents/runtime/lifecycle/index.mdx +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: Lifecycle -pcx_content_type: navigation -sidebar: - order: 1 - group: - hideIndex: true ---- - -import { DirectoryListing } from "~/components"; - - diff --git a/src/content/docs/agents/runtime/operations/index.mdx b/src/content/docs/agents/runtime/operations/index.mdx deleted file mode 100644 index a451c862df9..00000000000 --- a/src/content/docs/agents/runtime/operations/index.mdx +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: Operations -pcx_content_type: navigation -sidebar: - order: 4 - group: - hideIndex: true ---- - -import { DirectoryListing } from "~/components"; - - diff --git a/src/content/docs/agents/runtime/execution/agent-skills.mdx b/src/content/docs/agents/tools/agent-skills.mdx similarity index 99% rename from src/content/docs/agents/runtime/execution/agent-skills.mdx rename to src/content/docs/agents/tools/agent-skills.mdx index 7bef060ae58..265bac3e4b1 100644 --- a/src/content/docs/agents/runtime/execution/agent-skills.mdx +++ b/src/content/docs/agents/tools/agent-skills.mdx @@ -3,7 +3,7 @@ title: Agent Skills pcx_content_type: reference description: Give an agent a catalog of on-demand instructions, resources, and scripts with agents/skills, activated by the model only when a task matches. sidebar: - order: 17 + order: 8 products: - agents --- diff --git a/src/content/docs/agents/tools/browser.mdx b/src/content/docs/agents/tools/browser.mdx index 35e4b2e06f2..c8f4f35cdfa 100644 --- a/src/content/docs/agents/tools/browser.mdx +++ b/src/content/docs/agents/tools/browser.mdx @@ -31,7 +31,7 @@ Start with a [Pi Durable agent](/agents/harnesses/pi/). Install the browser runt -This example installs a `Browser` and `PiHarness` on a plain Durable Object. Pi chooses when to call the browser tool. [Lifecycle](/agents/runtime/lifecycle/) starts both components and provides their storage. +This example installs a `Browser` and `PiHarness` on a plain Durable Object. Pi chooses when to call the browser tool. [Lifecycle](/agents/lifecycle/) starts both components and provides their storage. diff --git a/src/content/docs/agents/tools/index.mdx b/src/content/docs/agents/tools/index.mdx index 56e6419587c..9464e5c5256 100644 --- a/src/content/docs/agents/tools/index.mdx +++ b/src/content/docs/agents/tools/index.mdx @@ -2,7 +2,7 @@ title: Tools pcx_content_type: navigation sidebar: - order: 4 + order: 6 group: hideIndex: true --- diff --git a/src/content/partials/agents/agent-class-legacy.mdx b/src/content/partials/agents/agent-class-legacy.mdx new file mode 100644 index 00000000000..2806bd5746d --- /dev/null +++ b/src/content/partials/agents/agent-class-legacy.mdx @@ -0,0 +1,9 @@ +--- +{} +--- + +:::note[Start new agents with a harness] + +The `Agent` class is the original way to build agents with the Agents SDK. It is still supported, but new agents should run a [harness](/agents/harnesses/) on a plain Durable Object and plug in only the [capabilities](/agents/capabilities/) they need. To get started that way, refer to the [Quick start](/agents/getting-started/quick-start/). + +::: From e9668103a79f3a205b4a7ecf861b4a264e01405f Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 27 Oct 2026 11:31:50 +0000 Subject: [PATCH 04/11] [Agents] Update harness docs for Pi 1.4 and agents@0.30, link the Codex example, and address review --- .../agents/2026-08-04-agent-tracing.mdx | 2 +- .../2026-10-20-think-computer-workspace.mdx | 2 +- .../agent-class/add-to-existing-project.mdx | 2 +- src/content/docs/agents/agent-class/index.mdx | 17 +++++++++++++- .../docs/agents/capabilities/index.mdx | 6 ++--- .../agents/getting-started/quick-start.mdx | 15 ++++++------ src/content/docs/agents/harnesses/index.mdx | 23 ++++++++++++++++--- src/content/docs/agents/index.mdx | 19 +++++++++++++-- src/content/docs/agents/lifecycle/index.mdx | 23 +++++++++++++++++-- .../docs/agents/lifecycle/job-queue.mdx | 2 ++ 10 files changed, 89 insertions(+), 22 deletions(-) diff --git a/src/content/changelog/agents/2026-08-04-agent-tracing.mdx b/src/content/changelog/agents/2026-08-04-agent-tracing.mdx index 59ef0618d69..8636c2710c2 100644 --- a/src/content/changelog/agents/2026-08-04-agent-tracing.mdx +++ b/src/content/changelog/agents/2026-08-04-agent-tracing.mdx @@ -40,7 +40,7 @@ Enable tracing in your Wrangler configuration: ## Framework support -Think and Flue agents are instrumented automatically. For direct AI SDK calls, wrap the namespace with [`wrapAISDK()`](/agents/runtime/operations/observability/tracing/#ai-sdk). Custom harnesses can use the [Workers custom spans API](/workers/observability/traces/custom-spans/) with [OpenTelemetry Generative AI semantic conventions](https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/). +Think and Flue agents are instrumented automatically. For direct AI SDK calls, wrap the namespace with [`wrapAISDK()`](/agents/platform/observability/tracing/#ai-sdk). Custom harnesses can use the [Workers custom spans API](/workers/observability/traces/custom-spans/) with [OpenTelemetry Generative AI semantic conventions](https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/). ## Get Started diff --git a/src/content/changelog/agents/2026-10-20-think-computer-workspace.mdx b/src/content/changelog/agents/2026-10-20-think-computer-workspace.mdx index d130c8e2703..31babb4f206 100644 --- a/src/content/changelog/agents/2026-10-20-think-computer-workspace.mdx +++ b/src/content/changelog/agents/2026-10-20-think-computer-workspace.mdx @@ -9,7 +9,7 @@ date: 2026-10-20 import { TypeScriptExample } from "~/components"; -[Think](/agents/harnesses/think/) and [Agent Skills](/agents/runtime/execution/agent-skills/) now use the workspace from [`@cloudflare/computer`](https://github.com/cloudflare/computer) as well as the filesystem tools. A JavaScript execution environment can be configured by adding the `WorkerJavaScriptBackend`. +[Think](/agents/harnesses/think/) and [Agent Skills](/agents/tools/agent-skills/) now use the workspace from [`@cloudflare/computer`](https://github.com/cloudflare/computer) as well as the filesystem tools. A JavaScript execution environment can be configured by adding the `WorkerJavaScriptBackend`. `@cloudflare/shell` is deprecated, and so is Think's `createExecuteTool`. These will no longer be developed nor supported from today onwards. diff --git a/src/content/docs/agents/agent-class/add-to-existing-project.mdx b/src/content/docs/agents/agent-class/add-to-existing-project.mdx index 2e0b9cf9528..4a49c9fb5e3 100644 --- a/src/content/docs/agents/agent-class/add-to-existing-project.mdx +++ b/src/content/docs/agents/agent-class/add-to-existing-project.mdx @@ -110,7 +110,7 @@ Add the Durable Object binding and migration: ## 4. Configure TypeScript and Vite -If you use `@callable()` decorators (as in the example above), you need two build configurations. +If you use `@callable()` decorators, as `CounterAgent` does, you need two build configurations. **tsconfig.json** — extend `agents/tsconfig` (or set `"target": "ES2021"` manually): diff --git a/src/content/docs/agents/agent-class/index.mdx b/src/content/docs/agents/agent-class/index.mdx index f19a485b5b9..6897b8e3102 100644 --- a/src/content/docs/agents/agent-class/index.mdx +++ b/src/content/docs/agents/agent-class/index.mdx @@ -41,11 +41,26 @@ To stay on the `Agent` class and add another capability, such as the [Pi harness ```ts import { Agent } from "agents"; +import { createModels } from "@earendil-works/pi-ai/models"; +import { createRegistry, Harness } from "@earendil-works/pi-durable"; import { PiHarness } from "agents/harness/pi"; +import { createAI } from "agents/models/pi-ai"; export class Assistant extends Agent { + ai = createAI({ binding: this.env.AI }); + registry = createRegistry(); + harness = new PiHarness({ - /* model and tools */ + harness: ({ storage, context, onWake }) => { + const models = createModels(); + models.setProvider(this.ai.provider); + return Harness.open( + storage, + { models, registry: this.registry, onWake }, + context, + ); + }, + defaults: { model: this.ai("@cf/zai-org/glm-5.3-flash") }, }); constructor(ctx: DurableObjectState, env: Env) { diff --git a/src/content/docs/agents/capabilities/index.mdx b/src/content/docs/agents/capabilities/index.mdx index 3114a7a8e44..e5072996584 100644 --- a/src/content/docs/agents/capabilities/index.mdx +++ b/src/content/docs/agents/capabilities/index.mdx @@ -24,7 +24,7 @@ import { Scheduler } from "agents/schedules"; import { WebSockets } from "agents/websockets"; export class MyAgent extends DurableObject { - readonly mcp = new MCPClientManager("my-agent", "1.0.0", { env: this.env }); + readonly mcp = new MCPClientManager("my-agent", "1.0.0"); readonly scheduler = new Scheduler({ callbacks: {} }); readonly webSockets = new WebSockets(); @@ -73,9 +73,7 @@ import { Lifecycle } from "agents/lifecycle"; import { MCPClientManager } from "agents/mcp/client"; export class ToolAgent extends DurableObject { - readonly mcp = new MCPClientManager("tool-agent", "1.0.0", { - env: this.env, - }); + readonly mcp = new MCPClientManager("tool-agent", "1.0.0"); readonly lifecycle = Lifecycle.install(this).use(this.mcp); } ``` diff --git a/src/content/docs/agents/getting-started/quick-start.mdx b/src/content/docs/agents/getting-started/quick-start.mdx index b05b08f3723..f9dba73d867 100644 --- a/src/content/docs/agents/getting-started/quick-start.mdx +++ b/src/content/docs/agents/getting-started/quick-start.mdx @@ -42,7 +42,7 @@ You do not need an API key. The agent uses [Workers AI](/workers-ai/) through th cd my-agent ``` - + @@ -93,15 +93,16 @@ export class Assistant extends DurableObject { readonly registry = createRegistry(); readonly harness = new PiHarness({ - harness: ({ storage, context }) => { + harness: ({ storage, context, onWake }) => { const models = createModels(); models.setProvider(this.ai.provider); - return Harness.open(storage, { models, registry: this.registry }, context); - }, - defaults: { - model: this.ai("@cf/zai-org/glm-4.7-flash"), - thinkingLevel: "low", + return Harness.open( + storage, + { models, registry: this.registry, onWake }, + context, + ); }, + defaults: { model: this.ai("@cf/zai-org/glm-5.3-flash") }, }); readonly lifecycle = Lifecycle.install(this).use(this.harness); diff --git a/src/content/docs/agents/harnesses/index.mdx b/src/content/docs/agents/harnesses/index.mdx index cd8be57dd94..4f03fa86c1c 100644 --- a/src/content/docs/agents/harnesses/index.mdx +++ b/src/content/docs/agents/harnesses/index.mdx @@ -16,7 +16,7 @@ The harness does the turn-by-turn work around the model: building the prompt, lo Running an agent on Cloudflare takes three steps: -1. **Pick a harness.** Install one of the [harnesses below](#choose-a-harness), such as the [Pi harness](/agents/harnesses/pi/), or [build your own](#build-your-own-harness). +1. **Pick a harness.** Install a [harness](#choose-a-harness), such as the [Pi harness](/agents/harnesses/pi/), or [build your own](#build-your-own-harness). 2. **Run it on a Durable Object.** Install the harness with the [Lifecycle](/agents/lifecycle/). The Lifecycle runs startup, routes requests, and owns the object's job queue and alarm, so the harness wakes and carries on after eviction, deploys, and crashes. 3. **Plug in capabilities.** Add an [MCP client](/agents/capabilities/#mcp-client), a [scheduler](/agents/capabilities/#scheduler), [WebSockets](/agents/capabilities/#websockets), or any other [capability](/agents/capabilities/) the agent needs. Each one is a field and one more `.use()` call. @@ -24,14 +24,29 @@ Running an agent on Cloudflare takes three steps: ```ts import { DurableObject } from "cloudflare:workers"; +import { createModels } from "@earendil-works/pi-ai/models"; +import { createRegistry, Harness } from "@earendil-works/pi-durable"; import { PiHarness } from "agents/harness/pi"; import { Lifecycle } from "agents/lifecycle"; +import { createAI } from "agents/models/pi-ai"; import { Scheduler } from "agents/schedules"; export class Assistant extends DurableObject { + readonly ai = createAI({ binding: this.env.AI }); + readonly registry = createRegistry(); + // The harness: the agent loop. readonly harness = new PiHarness({ - /* model and tools */ + harness: ({ storage, context, onWake }) => { + const models = createModels(); + models.setProvider(this.ai.provider); + return Harness.open( + storage, + { models, registry: this.registry, onWake }, + context, + ); + }, + defaults: { model: this.ai("@cf/zai-org/glm-5.3-flash") }, }); // A capability plugged in next to it. @@ -50,7 +65,7 @@ For a complete, runnable agent, refer to the [Quick start](/agents/getting-start ## Choose a harness -Except for Project Think, every harness below is a capability: it installs on a plain Durable Object with `Lifecycle.use()`, next to any other capability, and keeps many sessions per object through `sessions` and `session(id)`. Each session takes durable, idempotent submissions that you can wait on or abort. +Except for Project Think, every harness in this table is a capability: it installs on a plain Durable Object with `Lifecycle.use()`, next to any other capability, and keeps many sessions per object through `sessions` and `session(id)`. Each session takes durable, idempotent submissions that you can wait on or abort. | Harness | Import | Status | What runs the loop | | ----------------------------------------------------- | -------------------------- | ------------ | ------------------------------------------------------------------------ | @@ -86,6 +101,8 @@ store.enqueue({ session: "main", id: operationId, input: { text } }); store.append("main", "messages", [{ data: message }]); ``` +For a complete harness built this way, refer to the [Codex harness example](https://github.com/cloudflare/agents/tree/main/examples/next/harnesses/codex). It runs a turn loop derived from Codex, compiled to WebAssembly, as a capability on a plain Durable Object. The transcript lives in Sessions, operation events go to Streams, model and tool steps run as Tasks, and WebSockets serves the events to the browser. + ## What a harness owns A harness usually owns: diff --git a/src/content/docs/agents/index.mdx b/src/content/docs/agents/index.mdx index 87d4e77f1e2..704551f95e7 100644 --- a/src/content/docs/agents/index.mdx +++ b/src/content/docs/agents/index.mdx @@ -22,16 +22,31 @@ To run an agent, pick a harness and install it on a Durable Object with the [Lif ```ts import { DurableObject } from "cloudflare:workers"; +import { createModels } from "@earendil-works/pi-ai/models"; +import { createRegistry, Harness } from "@earendil-works/pi-durable"; import { PiHarness } from "agents/harness/pi"; import { Lifecycle } from "agents/lifecycle"; import { MCPClientManager } from "agents/mcp/client"; +import { createAI } from "agents/models/pi-ai"; import { Scheduler } from "agents/schedules"; export class Assistant extends DurableObject { + readonly ai = createAI({ binding: this.env.AI }); + readonly registry = createRegistry(); + readonly harness = new PiHarness({ - /* model and tools */ + harness: ({ storage, context, onWake }) => { + const models = createModels(); + models.setProvider(this.ai.provider); + return Harness.open( + storage, + { models, registry: this.registry, onWake }, + context, + ); + }, + defaults: { model: this.ai("@cf/zai-org/glm-5.3-flash") }, }); - readonly mcp = new MCPClientManager("assistant", "1.0.0", { env: this.env }); + readonly mcp = new MCPClientManager("assistant", "1.0.0"); readonly scheduler = new Scheduler({ callbacks: {} }); readonly lifecycle = Lifecycle.install(this) diff --git a/src/content/docs/agents/lifecycle/index.mdx b/src/content/docs/agents/lifecycle/index.mdx index ce9e38df249..7fcd0197dc5 100644 --- a/src/content/docs/agents/lifecycle/index.mdx +++ b/src/content/docs/agents/lifecycle/index.mdx @@ -14,6 +14,25 @@ import { PackageManagers, TypeScriptExample } from "~/components"; Every agent on Cloudflare is a [harness](/agents/harnesses/) running on a [Durable Object](/durable-objects/). `Lifecycle` from `agents/lifecycle` is what keeps it running there. It runs startup, offers each request to the harness and the other installed [capabilities](/agents/capabilities/), and owns the object's durable job queue and its single alarm. That is how a harness wakes and finishes its work after eviction, deploys, and crashes. +```mermaid +flowchart TB + R["Request, WebSocket message, or alarm"] --> L + subgraph DO["Durable Object: one per user, conversation, or task"] + L["Lifecycle
runs startup, routes each event,
owns the job queue and the one alarm"] + L -->|"onStart, onRequest, onJob"| H["Harness, such as PiHarness
runs the agent loop"] + L -->|"onStart, onRequest, onJob"| C["Capabilities, such as Scheduler,
MCPClientManager, and WebSockets"] + H --- DB[("SQLite storage")] + C --- DB + end +``` + +The roles split like this: + +- **The Durable Object** is where the agent lives. It gives each agent an address, its own SQLite database, and one alarm. +- **The Lifecycle** decides when code runs. It starts every installed capability once per wake, offers each request and WebSocket event to them in order, and runs due jobs when the alarm fires. +- **The harness** runs the agent loop: the model calls, tool calls, and transcript. It stores its work in the object's database and queues jobs so the Lifecycle can wake it to finish. +- **Capabilities** add everything else, such as schedules, MCP connections, and real-time clients. Each one is independent of the others and of the harness. + `agents/lifecycle` is stable. You install the Lifecycle on a class that extends `DurableObject`. Most agents only need `Lifecycle.install(this)` and one `.use()` call for each capability. This section covers what the Lifecycle does underneath, and how to [build your own capability](/agents/lifecycle/build-a-capability/). The legacy [`Agent` class](/agents/agent-class/) installs the same Lifecycle for you. ## Install the Lifecycle @@ -91,7 +110,7 @@ import { MCPClientManager } from "agents/mcp/client"; import { Scheduler } from "agents/schedules"; export class MyAgent extends DurableObject { - readonly mcp = new MCPClientManager("my-agent", "1.0.0", { env: this.env }); + readonly mcp = new MCPClientManager("my-agent", "1.0.0"); readonly scheduler = new Scheduler({ callbacks: { sendReminder: (payload: { message: string }) => { @@ -149,7 +168,7 @@ Lifecycle starts on the first request, alarm, or WebSocket event of each in-memo 2. The host runs `onStart()`. 3. Lifecycle handles the event that woke the object. -A warm object skips startup. If startup fails, the event fails and the next event tries again. +A warm object skips startup. If startup fails, the event fails and the next event tries again. If the job queue holds jobs, a fallback alarm also retries startup, so queued work does not wait for a new request. For more information, refer to [Jobs and alarms](/agents/lifecycle/job-queue/#retries-and-failures). For an HTTP request, Lifecycle offers the request to each capability in registration order. The first capability that returns a `Response` handles it. A capability that returns `undefined` passes the request on. A request that no capability handles reaches the host's `onRequest()`. diff --git a/src/content/docs/agents/lifecycle/job-queue.mdx b/src/content/docs/agents/lifecycle/job-queue.mdx index d7d10b3c376..f746013a288 100644 --- a/src/content/docs/agents/lifecycle/job-queue.mdx +++ b/src/content/docs/agents/lifecycle/job-queue.mdx @@ -94,6 +94,8 @@ A capability can implement `onJobError(context, error)` to decide what happens a Before it runs any job, Lifecycle sets a backup alarm. If the isolate dies mid-run, that alarm wakes the object to resume the work. +While the queue holds jobs, Lifecycle also sets a fallback alarm before startup runs, at the next job's time but at least 30 seconds out. If startup throws, that alarm retries it, so queued jobs are not stranded by a failed startup. + ## How the alarm runs When the alarm fires, Lifecycle: From 09c61e102ca2d6f3ea52d8b93b580677c4e50498 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 27 Oct 2026 12:02:47 +0000 Subject: [PATCH 05/11] [Agents] Remove the AI SDK harness docs --- src/components/cf/AgentsPlatformDiagram.astro | 5 +- src/content/docs/agents/harnesses/ai-sdk.mdx | 106 ------------------ src/content/docs/agents/harnesses/index.mdx | 3 - src/content/docs/agents/index.mdx | 2 +- 4 files changed, 5 insertions(+), 111 deletions(-) delete mode 100644 src/content/docs/agents/harnesses/ai-sdk.mdx diff --git a/src/components/cf/AgentsPlatformDiagram.astro b/src/components/cf/AgentsPlatformDiagram.astro index ec81fc77e4b..c9e41871904 100644 --- a/src/components/cf/AgentsPlatformDiagram.astro +++ b/src/components/cf/AgentsPlatformDiagram.astro @@ -96,7 +96,6 @@ const tools = [
Pi OpenCode - AI SDK Containers Project Think @@ -396,6 +395,10 @@ const tools = [ margin-top: 9px; } + .section-row a:last-child:nth-child(odd) { + grid-column: 1 / -1; + } + .section-row a, .agent-class, .runtime-list a { diff --git a/src/content/docs/agents/harnesses/ai-sdk.mdx b/src/content/docs/agents/harnesses/ai-sdk.mdx deleted file mode 100644 index 8700328fe08..00000000000 --- a/src/content/docs/agents/harnesses/ai-sdk.mdx +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: AI SDK harness -pcx_content_type: how-to -description: Run an AI SDK streamText loop on a Durable Object with the experimental AiSdkHarness capability, with sessions, transcripts, and a queue of operations in the object's storage. -sidebar: - order: 3 - badge: Experimental -products: - - agents ---- - -import { PackageManagers, TypeScriptExample } from "~/components"; - -`AiSdkHarness` from `agents/harness/ai-sdk` runs the [AI SDK](https://ai-sdk.dev/)'s `streamText` as a harness on a Durable Object. It keeps sessions, their transcripts, and a queue of operations in the object's storage. One operation runs at a time per session, and each run is one `streamText` call over the session's transcript. - -:::caution[Experimental] -`AiSdkHarness` is experimental. The API may change in any release. -::: - -Use it when you want a thin, AI SDK-native loop. `AiSdkHarness` implements the shared harness interface that the experimental Channels API serves, so the same harness can answer browsers, terminals, and messaging platforms. - -## Install - - - -The example uses [Workers AI](/workers-ai/) through the `AI` binding and `createAI` from `agents/models/ai-sdk`. To use other models, refer to [AI SDK models](/agents/models/ai-sdk/). - -## Create the harness - - - -```ts -import { DurableObject } from "cloudflare:workers"; -import { stepCountIs, tool } from "ai"; -import { z } from "zod"; -import { AiSdkHarness } from "agents/harness/ai-sdk"; -import { Lifecycle } from "agents/lifecycle"; -import { createAI } from "agents/models/ai-sdk"; - -export class Assistant extends DurableObject { - readonly ai = createAI({ binding: this.env.AI }); - - readonly harness = new AiSdkHarness({ - model: this.ai("@cf/moonshotai/kimi-k2.7-code"), - system: "You are a helpful assistant.", - tools: { - flipCoin: tool({ - description: "Flip a coin, once the user approves", - inputSchema: z.object({}), - needsApproval: true, - execute: async () => (Math.random() < 0.5 ? "Heads" : "Tails"), - }), - }, - stopWhen: stepCountIs(5), - }); - - readonly lifecycle = Lifecycle.install(this).use(this.harness); - - async onRequest(request: Request): Promise { - const session = this.harness.session(); - const { operationId } = await session.submit({ - parts: [{ type: "text", text: await request.text() }], - }); - return Response.json(await session.wait(operationId, request.signal)); - } -} -``` - - - -| Option | Description | -| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `model` | Required. An AI SDK `LanguageModel`. | -| `tools` | An AI SDK `ToolSet`. Tools without `execute` run on the client of the participant whose message started the turn. | -| `system` | The system prompt. | -| `stopWhen` | When the run stops calling the model. Defaults to `stepCountIs(5)`. | -| `heartbeatMs` | How often a running session's wake job checks in. Defaults to 30 seconds. | - -## Work with sessions - -`harness.session(id)` returns a handle on one session. Without an id, it returns the default session. Each session has: - -| Method | Description | -| ---------------------------- | ------------------------------------------------------------------------------------------------------- | -| `submit(input, options)` | Queue a message or a tool answer. Pass `operationId` to make the call idempotent. | -| `wait(operationId, signal?)` | Resolve when the operation settles, as `done` with its `text` or `unanswered` with a `reason`. | -| `abort(operationId?)` | Stop one operation, or everything in the session. | -| `reset(handoff?)` | Start a new context, optionally from a handoff note. | -| `watch()` | The current state, then every change in order: operation status, run start and end, chunks, and saved messages. | - -`harness.sessions` has `create()`, `fork(from)`, and `list()`. - -A tool call that needs approval settles the operation. Submit the answer as a new operation to continue: - -```ts -await session.submit({ type: "approval", approvalId, approved: true }); -``` - -## Recovery - -The AI SDK keeps no state of its own. If an eviction cuts a run short, the harness starts the run again from the saved transcript, and the run's unsaved output is lost. Steering is not supported: `whenBusy: "steer"` queues the input as a follow-up. - -## Related resources - -- [Harnesses](/agents/harnesses/) compares the harnesses you can run. -- [AI SDK models](/agents/models/ai-sdk/) configures Workers AI and third-party models. diff --git a/src/content/docs/agents/harnesses/index.mdx b/src/content/docs/agents/harnesses/index.mdx index 4f03fa86c1c..ee15ff57fb4 100644 --- a/src/content/docs/agents/harnesses/index.mdx +++ b/src/content/docs/agents/harnesses/index.mdx @@ -70,15 +70,12 @@ Except for Project Think, every harness in this table is a capability: it instal | Harness | Import | Status | What runs the loop | | ----------------------------------------------------- | -------------------------- | ------------ | ------------------------------------------------------------------------ | | [Pi](/agents/harnesses/pi/) | `agents/harness/pi` | Beta | [Pi Durable](https://earendil.com/posts/pi-durable/) | -| [AI SDK harness](/agents/harnesses/ai-sdk/) | `agents/harness/ai-sdk` | Experimental | The AI SDK's `streamText` | | [Container harness](/agents/harnesses/container/) | `agents/harness/container` | Experimental | Claude Code, Codex, or another agent CLI in a Cloudflare Container | | [OpenCode](/agents/harnesses/opencode/) | `agents/harness/opencode` | Experimental | [OpenCode](https://opencode.ai/) 2.0, inside the Durable Object | | [Project Think](/agents/harnesses/think/) | `@cloudflare/think` | Experimental | Think's agent loop, as a class that extends the `Agent` class | Use **Pi** when you want Pi Durable to own the agent loop, transcript, and recovery, including steering a running turn. You choose how clients reach it, such as HTTP, WebSockets, or RPC. Start here if you are not sure. -Use the **AI SDK harness** when you want a thin `streamText` loop with sessions and a queue of operations, and nothing more. - Use the **Container harness** when you want a coding agent CLI with a real filesystem and shell. The CLI runs in a container that stops when idle, while the Durable Object keeps the sessions. Use **OpenCode** when you want OpenCode 2.0's sessions, tools, and plugin system, running inside the Durable Object itself. diff --git a/src/content/docs/agents/index.mdx b/src/content/docs/agents/index.mdx index 704551f95e7..3928a45e550 100644 --- a/src/content/docs/agents/index.mdx +++ b/src/content/docs/agents/index.mdx @@ -64,7 +64,7 @@ Deploy once, and Cloudflare runs your agents across its global network, scaling An agent on Cloudflare has these parts: -- **A harness** is the agent. It runs the loop around the model: prompts, tool calls, retries, and recovery. Start with the [Pi harness](/agents/harnesses/pi/), or one of the experimental harnesses: the [AI SDK harness](/agents/harnesses/ai-sdk/), [OpenCode](/agents/harnesses/opencode/), or the [Container harness](/agents/harnesses/container/), which runs Claude Code or Codex. You can also extend [Project Think](/agents/harnesses/think/), or [build your own](/agents/harnesses/#build-your-own-harness). +- **A harness** is the agent. It runs the loop around the model: prompts, tool calls, retries, and recovery. Start with the [Pi harness](/agents/harnesses/pi/), or one of the experimental harnesses: [OpenCode](/agents/harnesses/opencode/) or the [Container harness](/agents/harnesses/container/), which runs Claude Code or Codex. You can also extend [Project Think](/agents/harnesses/think/), or [build your own](/agents/harnesses/#build-your-own-harness). - **A Durable Object** is where the agent lives. Each instance has its own storage and address, so each user or task gets its own agent. - **The Lifecycle** keeps the harness running on the Durable Object. It runs startup, routes requests, and owns the object's durable job queue and its single alarm, so work continues across eviction, deploys, and crashes. - **Capabilities** plug in what else the agent needs: an [MCP client](/agents/capabilities/#mcp-client), [schedules](/agents/capabilities/#scheduler), [queues](/agents/capabilities/#queue), [WebSockets](/agents/capabilities/#websockets) and [state](/agents/capabilities/#state) for clients, and [routed agents](/agents/capabilities/#routed-agents). From eeae8230b7c0780e5c475471ca0014dc90d9fdb2 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 27 Oct 2026 12:40:24 +0000 Subject: [PATCH 06/11] [Agents] Reword catch-alls for the typos check --- src/content/docs/agents/agent-class/internals.mdx | 2 +- src/content/docs/agents/lifecycle/index.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/docs/agents/agent-class/internals.mdx b/src/content/docs/agents/agent-class/internals.mdx index 4bce519f0be..391c6f01d9e 100644 --- a/src/content/docs/agents/agent-class/internals.mdx +++ b/src/content/docs/agents/agent-class/internals.mdx @@ -118,7 +118,7 @@ Lastly, it is worth mentioning that the Durable Object also has the Worker `Env` `Agent` uses `Lifecycle.install(this)` to install its platform-facing request, alarm, and hibernating WebSocket event handlers. Agent subclasses implement semantic callbacks such as `onStart()` and `onRequest()` instead of forwarding those platform events. -The `Agent` class installs `State`, `Queue`, `Scheduler`, `Tasks`, `MCPClientManager`, and `WebSockets`, plus its internal dynamic-agent capability. Capabilities start before Agent startup. Selective capabilities run before catch-alls, such as `WebSockets`. Queues, schedules, and tasks share one Lifecycle-managed Durable Object alarm. +The `Agent` class installs `State`, `Queue`, `Scheduler`, `Tasks`, `MCPClientManager`, and `WebSockets`, plus its internal dynamic-agent capability. Capabilities start before Agent startup. Selective capabilities run before catch-all capabilities, such as `WebSockets`. Queues, schedules, and tasks share one Lifecycle-managed Durable Object alarm. You can install additional capabilities through `this.lifecycle.use(capability)`. The same capability can also run on a class that directly extends `DurableObject`. diff --git a/src/content/docs/agents/lifecycle/index.mdx b/src/content/docs/agents/lifecycle/index.mdx index 7fcd0197dc5..9325ca073bf 100644 --- a/src/content/docs/agents/lifecycle/index.mdx +++ b/src/content/docs/agents/lifecycle/index.mdx @@ -182,7 +182,7 @@ routeAgentRequest(request) └─ host onRequest() ``` -A capability declares `claims: "catch-all"` when it handles everything offered to a hook. Lifecycle always runs catch-alls after every selective capability. The `WebSockets` capability is a catch-all for upgrades, so other capabilities can claim their own upgrade routes first. Without a capability that accepts upgrades, Lifecycle declines WebSocket upgrades. +A capability declares `claims: "catch-all"` when it handles everything offered to a hook. Lifecycle always runs catch-all capabilities after every selective capability. The `WebSockets` capability is a catch-all for upgrades, so other capabilities can claim their own upgrade routes first. Without a capability that accepts upgrades, Lifecycle declines WebSocket upgrades. There is no `next()` callback. A capability either handles a request or declines it. From 9c5077e5cb3e9256585cf40ca223fc82b7914151 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 27 Oct 2026 11:10:55 +0000 Subject: [PATCH 07/11] [Agents] Document each capability on its own page --- src/components/cf/AgentsPlatformDiagram.astro | 12 +- src/content/docs/agents/agent-class/index.mdx | 16 +- .../docs/agents/capabilities/browser.mdx | 125 +++ .../docs/agents/capabilities/index.mdx | 365 ++----- .../docs/agents/capabilities/mcp-client.mdx | 166 ++++ .../docs/agents/capabilities/queue.mdx | 124 +++ .../agents/capabilities/routed-agents.mdx | 119 +++ .../docs/agents/capabilities/scheduler.mdx | 125 +++ .../docs/agents/capabilities/sessions.mdx | 910 ++---------------- .../docs/agents/capabilities/state.mdx | 120 +++ .../docs/agents/capabilities/streams.mdx | 186 ++++ .../docs/agents/capabilities/tasks.mdx | 193 ++++ .../docs/agents/capabilities/websockets.mdx | 161 ++++ .../conversation-state-and-memory.mdx | 6 +- .../add-to-existing-project.mdx | 2 +- src/content/docs/agents/harnesses/index.mdx | 2 +- .../docs/agents/harnesses/pi/index.mdx | 4 +- .../docs/agents/harnesses/think/recovery.mdx | 4 +- .../docs/agents/harnesses/think/tools.mdx | 2 +- src/content/docs/agents/index.mdx | 2 +- .../docs/agents/lifecycle/job-queue.mdx | 2 +- 21 files changed, 1541 insertions(+), 1105 deletions(-) create mode 100644 src/content/docs/agents/capabilities/browser.mdx create mode 100644 src/content/docs/agents/capabilities/mcp-client.mdx create mode 100644 src/content/docs/agents/capabilities/queue.mdx create mode 100644 src/content/docs/agents/capabilities/routed-agents.mdx create mode 100644 src/content/docs/agents/capabilities/scheduler.mdx create mode 100644 src/content/docs/agents/capabilities/state.mdx create mode 100644 src/content/docs/agents/capabilities/streams.mdx create mode 100644 src/content/docs/agents/capabilities/tasks.mdx create mode 100644 src/content/docs/agents/capabilities/websockets.mdx diff --git a/src/components/cf/AgentsPlatformDiagram.astro b/src/components/cf/AgentsPlatformDiagram.astro index c9e41871904..ef642f4b4cb 100644 --- a/src/components/cf/AgentsPlatformDiagram.astro +++ b/src/components/cf/AgentsPlatformDiagram.astro @@ -28,12 +28,12 @@ const channels = [ ]; const runtime = [ - { label: "MCP client", href: "/agents/capabilities/#mcp-client" }, - { label: "Scheduler", href: "/agents/capabilities/#scheduler" }, - { label: "Queue", href: "/agents/capabilities/#queue" }, - { label: "WebSockets", href: "/agents/capabilities/#websockets" }, - { label: "Sessions", href: "/agents/capabilities/#sessions" }, - { label: "Tasks", href: "/agents/capabilities/#tasks" }, + { label: "MCP client", href: "/agents/capabilities/mcp-client/" }, + { label: "Scheduler", href: "/agents/capabilities/scheduler/" }, + { label: "Queue", href: "/agents/capabilities/queue/" }, + { label: "WebSockets", href: "/agents/capabilities/websockets/" }, + { label: "Sessions", href: "/agents/capabilities/sessions/" }, + { label: "Tasks", href: "/agents/capabilities/tasks/" }, ]; const tools = [ diff --git a/src/content/docs/agents/agent-class/index.mdx b/src/content/docs/agents/agent-class/index.mdx index 6897b8e3102..a9dd6e4ddc3 100644 --- a/src/content/docs/agents/agent-class/index.mdx +++ b/src/content/docs/agents/agent-class/index.mdx @@ -30,12 +30,12 @@ Each `Agent` API calls a capability underneath. To move an agent off the `Agent` | Agent API | Capability | | ---------------------------------------------------------------------- | ------------------------------------------------ | -| `this.schedule()`, `this.scheduleEvery()`, `this.cancelSchedule()` | [`Scheduler`](/agents/capabilities/#scheduler) | -| `this.queue()`, `this.dequeue()`, `this.getQueue()` | [`Queue`](/agents/capabilities/#queue) | -| `this.addMcpServer()`, `this.removeMcpServer()`, `this.mcp` | [`MCPClientManager`](/agents/capabilities/#mcp-client) | -| `this.state`, `this.setState()`, `onStateChanged()` | [`State`](/agents/capabilities/#state) | -| `onConnect()`, `onMessage()`, `this.broadcast()`, `getConnections()` | [`WebSockets`](/agents/capabilities/#websockets) | -| `this.tasks` | [`Tasks`](/agents/capabilities/#tasks) | +| `this.schedule()`, `this.scheduleEvery()`, `this.cancelSchedule()` | [`Scheduler`](/agents/capabilities/scheduler/) | +| `this.queue()`, `this.dequeue()`, `this.getQueue()` | [`Queue`](/agents/capabilities/queue/) | +| `this.addMcpServer()`, `this.removeMcpServer()`, `this.mcp` | [`MCPClientManager`](/agents/capabilities/mcp-client/) | +| `this.state`, `this.setState()`, `onStateChanged()` | [`State`](/agents/capabilities/state/) | +| `onConnect()`, `onMessage()`, `this.broadcast()`, `getConnections()` | [`WebSockets`](/agents/capabilities/websockets/) | +| `this.tasks` | [`Tasks`](/agents/capabilities/tasks/) | To stay on the `Agent` class and add another capability, such as the [Pi harness](/agents/harnesses/pi/), install it on `this.lifecycle` in the constructor: @@ -161,7 +161,7 @@ flowchart TD | **Sub-agents** | `subAgent()`, `abortSubAgent()`, `deleteSubAgent()` | [Sub-agents](/agents/agent-class/sub-agents/) | | **Agents as tools** | `runAgentTool()`, `clearAgentToolRuns()`, `hasAgentToolRun()` | [Agents as tools](/agents/agent-class/agent-tools/) | | **Agent Skills** | `skills` registry, bundled skill sources, script runners | [Agent Skills](/agents/tools/agent-skills/) | -| **Sessions** | `Session.create()`, context blocks, compaction, search | [Sessions](/agents/capabilities/sessions/) | +| **Sessions** | `Sessions` capability: message trees, compaction, search | [Sessions](/agents/capabilities/sessions/) | | **Think** | `Think` base class, workspace tools, lifecycle hooks, extensions | [Think](/agents/harnesses/think/) | | **Chat SDK** | `createChatSdkState()`, `ChatSdkStateAgent` | [Chat SDK](/agents/agent-class/communication-channels/chat-sdk/) | @@ -253,7 +253,7 @@ import { routeAgentRequest } from "agents"; export default { async fetch(request: Request, env: Env) { return ( - routeAgentRequest(request, env) || + (await routeAgentRequest(request, env)) ?? new Response("Not found", { status: 404 }) ); }, diff --git a/src/content/docs/agents/capabilities/browser.mdx b/src/content/docs/agents/capabilities/browser.mdx new file mode 100644 index 00000000000..3453cf492a7 --- /dev/null +++ b/src/content/docs/agents/capabilities/browser.mdx @@ -0,0 +1,125 @@ +--- +title: Browser +description: Keep one persistent Browser Run session open for a Durable Object with the Browser capability, and give it to a model as a tool. +pcx_content_type: reference +sidebar: + order: 3 + badge: Experimental +products: + - agents +--- + +import { PackageManagers, TypeScriptExample, WranglerConfig } from "~/components"; + +:::caution[Experimental] +The `Browser` capability may change between releases. +::: + +`Browser` from `agents/browser` gives an agent one [Browser Run](/browser-run/) session that stays open between requests. Tabs, cookies, and logins carry over from one model turn to the next. The capability stores the session in the object's database and reconnects to it after a wake. + +For one-off page reads, such as fetching a page as Markdown, use the [`web_fetch` tool](/agents/tools/web-fetch/) instead. It needs no browser. + +## Install the capability + + + +Add the Browser Run and Worker Loader bindings: + + + +```jsonc +{ + "compatibility_flags": ["nodejs_compat"], + "browser": { "binding": "BROWSER" }, + "worker_loaders": [{ "binding": "LOADER" }], +} +``` + + + +Construct the browser with a Browser Run provider, and install it on the [Lifecycle](/agents/lifecycle/): + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Browser, browserRun } from "agents/browser"; +import { Lifecycle } from "agents/lifecycle"; + +export class ResearchAgent extends DurableObject { + readonly browser = new Browser({ + provider: browserRun(this.env.BROWSER, { keepAliveMs: 10 * 60_000 }), + }); + readonly lifecycle = Lifecycle.install(this).use(this.browser); +} +``` + + + +| Option | Description | +| ----------------- | ----------------------------------------------------------------------------------- | +| `provider` | Where the browser runs. Create one with `browserRun(binding, options)` | +| `name` | Which browser this is. Use a different name for each extra browser on one object | +| `timeoutMs` | Default timeout for Chrome DevTools Protocol (CDP) commands | + +`browserRun()` accepts Browser Run options such as `keepAliveMs`, `recording`, and hostname `guardrails`. + +## Give the browser to a model + +`browserTool()` from `agents/browser/ai-sdk` turns the browser into one AI SDK tool. The model writes CDP code, and the tool runs it in a sandbox against the persistent browser: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Browser, browserRun } from "agents/browser"; +import { browserTool } from "agents/browser/ai-sdk"; +import { Lifecycle } from "agents/lifecycle"; +import { generateText, type LanguageModel } from "ai"; + +export { CodemodeRuntime } from "agents/browser"; + +declare const model: LanguageModel; + +export class ResearchAgent extends DurableObject { + readonly browser = new Browser({ provider: browserRun(this.env.BROWSER) }); + readonly lifecycle = Lifecycle.install(this).use(this.browser); + + async onRequest(request: Request): Promise { + const { text } = await generateText({ + model, + prompt: await request.text(), + tools: { + browser: browserTool({ + ctx: this.ctx, + browser: this.browser, + loader: this.env.LOADER, + }), + }, + }); + return new Response(text); + } +} +``` + + + +Export `CodemodeRuntime` from your Worker's entry point. The tool's sandbox runs in a Durable Object facet of that class, so pass `ctx` on a plain Durable Object. + +- The model never opens or closes the browser. If the session was lost, the code runs in a new browser and the result includes `restarted: true`. +- The tab the model works in is remembered between turns. Tabs a page opens come back as `newTabs`. +- For Pi, import `browserTool` from `agents/browser/pi`. For TanStack AI, import it from `agents/browser/tanstack-ai`. +- When several conversations share one `Browser`, pass `scope` with a conversation ID so each one keeps its own active tab. + +## Control the browser from your code + +| Method | Description | +| -------------------------- | --------------------------------------------------------------- | +| `connect()` | Opens a CDP connection to the session, creating it if needed | +| `liveView()` | Returns a [Live View](/browser-run/features/live-view/) link so a person can watch or take over | +| `close()` | Closes the session. The next use starts a new browser | + +## Related resources + +- [Browser tools](/agents/tools/browser/) covers `browserTool` with Pi and other harnesses, Live View, human takeover, recording, and the legacy `createBrowserTools()`. +- [Browser agent](/agents/examples/browser-agent/) builds a complete agent that uses a browser. diff --git a/src/content/docs/agents/capabilities/index.mdx b/src/content/docs/agents/capabilities/index.mdx index e5072996584..0e7e6c9281b 100644 --- a/src/content/docs/agents/capabilities/index.mdx +++ b/src/content/docs/agents/capabilities/index.mdx @@ -1,6 +1,6 @@ --- title: Capabilities -description: Add a harness, MCP client, scheduling, queues, WebSockets, state, sessions, streams, and durable tasks to a Durable Object with Lifecycle capabilities. +description: Plug capabilities into your agent next to its harness — MCP clients, scheduling, queues, WebSockets, state, sessions, streams, and durable tasks. pcx_content_type: concept sidebar: order: 5 @@ -12,23 +12,42 @@ products: import { TypeScriptExample } from "~/components"; -A capability is a reusable piece of a Durable Object. Each one owns its tables and behavior, exposes a focused API, and implements only the [Lifecycle](/agents/lifecycle/) hooks it needs. You build an agent by installing the capabilities you want on one Durable Object: +Your agent is a [harness](/agents/harnesses/) running on a Durable Object. Capabilities are what you plug in next to it: an MCP client, a scheduler, real-time clients, and more. Each capability owns its tables and behavior, exposes a focused API, and implements only the [Lifecycle](/agents/lifecycle/) hooks it needs. Plug one in with a field and one more `.use()` call: ```ts import { DurableObject } from "cloudflare:workers"; +import { createModels } from "@earendil-works/pi-ai/models"; +import { createRegistry, Harness } from "@earendil-works/pi-durable"; +import { PiHarness } from "agents/harness/pi"; import { Lifecycle } from "agents/lifecycle"; import { MCPClientManager } from "agents/mcp/client"; +import { createAI } from "agents/models/pi-ai"; import { Scheduler } from "agents/schedules"; import { WebSockets } from "agents/websockets"; export class MyAgent extends DurableObject { + readonly ai = createAI({ binding: this.env.AI }); + readonly registry = createRegistry(); + readonly harness = new PiHarness({ + harness: ({ storage, context, onWake }) => { + const models = createModels(); + models.setProvider(this.ai.provider); + return Harness.open( + storage, + { models, registry: this.registry, onWake }, + context, + ); + }, + defaults: { model: this.ai("@cf/zai-org/glm-5.3-flash") }, + }); readonly mcp = new MCPClientManager("my-agent", "1.0.0"); readonly scheduler = new Scheduler({ callbacks: {} }); readonly webSockets = new WebSockets(); readonly lifecycle = Lifecycle.install(this) + .use(this.harness) .use(this.mcp) .use(this.scheduler) .use(this.webSockets); @@ -37,306 +56,128 @@ export class MyAgent extends DurableObject { -Capabilities share the object's storage and its alarm, but they do not depend on each other. Install each one once. Bindings and callbacks a capability needs go in its constructor. +The harness and the capabilities share the object's storage and its alarm, but they do not depend on each other. Install each one once. Bindings and callbacks a capability needs go in its constructor. ## Available capabilities -The following capabilities ship with the `agents` package. Capabilities marked Experimental may change between releases. +The following capabilities ship with the `agents` package. Capabilities marked Experimental may change between releases. Harnesses are capabilities too. For the harnesses and their status, refer to [Choose a harness](/agents/harnesses/#choose-a-harness). | Capability | Import | Status | What it does | | ------------------------------------- | ------------------- | ------------ | -------------------------------------------------------------------- | -| [`PiHarness`](/agents/harnesses/pi/) | `agents/harness/pi` | Beta | Runs the Pi agent loop, with recovery after eviction | -| [`MCPClientManager`](#mcp-client) | `agents/mcp/client` | Stable | Connects to MCP servers and keeps the connections across restarts | -| [`Scheduler`](#scheduler) | `agents/schedules` | Stable | Runs callbacks after a delay, at a date, on a cron, or on an interval | -| [`WebSockets`](#websockets) | `agents/websockets` | Stable | Accepts hibernating connections that `useAgent` can reach | -| [`State`](#state) | `agents/state` | Stable | Stores one validated value, and syncs it to clients with `WebSockets` | -| [`Queue`](#queue) | `agents/queue` | Experimental | Runs background callbacks one at a time, in push order | -| [`RoutedAgents`](#routed-agents) | `agents/routing` | Experimental | Keeps a catalog of independent agents and forwards requests to them | -| [`Browser`](#browser) | `agents/browser` | Experimental | Keeps one persistent browser session open for the agent | -| [`Sessions`](#sessions) | `agents/sessions` | Experimental | Stores conversation history as a tree of messages | -| [`Streams`](#streams) | `agents/streams` | Experimental | Stores incremental output that clients can replay and follow | -| [`Tasks`](#tasks) | `agents/tasks` | Experimental | Runs replayable background work with journaled steps | - -`PiHarness`, `MCPClientManager`, `RoutedAgents`, and `Browser` give an agent its behavior. `Scheduler`, `Queue`, `WebSockets`, and `State` cover background work and connections. `Sessions`, `Streams`, and `Tasks` are lower-level pieces for [building your own harness](#build-your-own-harness). +| [`MCPClientManager`](/agents/capabilities/mcp-client/) | `agents/mcp/client` | Stable | Connects to MCP servers and keeps the connections across restarts | +| [`Scheduler`](/agents/capabilities/scheduler/) | `agents/schedules` | Stable | Runs callbacks after a delay, at a date, on a cron, or on an interval | +| [`WebSockets`](/agents/capabilities/websockets/) | `agents/websockets` | Stable | Accepts hibernating connections that `useAgent` can reach | +| [`State`](/agents/capabilities/state/) | `agents/state` | Stable | Stores one validated value, and syncs it to clients with `WebSockets` | +| [`Queue`](/agents/capabilities/queue/) | `agents/queue` | Experimental | Runs background callbacks one at a time, in push order | +| [`RoutedAgents`](/agents/capabilities/routed-agents/) | `agents/routing` | Experimental | Keeps a catalog of independent agents and forwards requests to them | +| [`Browser`](/agents/capabilities/browser/) | `agents/browser` | Experimental | Keeps one persistent browser session open for the agent | +| [`Sessions`](/agents/capabilities/sessions/) | `agents/sessions` | Experimental | Stores conversation history as a tree of messages | +| [`Streams`](/agents/capabilities/streams/) | `agents/streams` | Experimental | Stores incremental output that clients can replay and follow | +| [`Tasks`](/agents/capabilities/tasks/) | `agents/tasks` | Experimental | Runs replayable background work with journaled steps | To write a capability of your own, refer to [Build a capability](/agents/lifecycle/build-a-capability/). -## MCP client - -`MCPClientManager` connects your agent to [Model Context Protocol (MCP)](/agents/model-context-protocol/) servers. It stores the servers you add, reconnects to them on startup, and handles OAuth callbacks before your `onRequest()` runs: - - - -```ts -import { DurableObject } from "cloudflare:workers"; -import { Lifecycle } from "agents/lifecycle"; -import { MCPClientManager } from "agents/mcp/client"; - -export class ToolAgent extends DurableObject { - readonly mcp = new MCPClientManager("tool-agent", "1.0.0"); - readonly lifecycle = Lifecycle.install(this).use(this.mcp); -} -``` - - - -Pass `env` when you connect to MCP servers over Durable Object RPC. Use `listTools()` for the raw tool list or `getAITools()` for AI SDK tools. For more information, refer to the [MCP client API](/agents/model-context-protocol/apis/client-api/). - -## Scheduler - -`Scheduler` runs registered callbacks after a delay, at a date, from a cron expression, or at a fixed interval: - - - -```ts -import { DurableObject } from "cloudflare:workers"; -import { Lifecycle } from "agents/lifecycle"; -import { Scheduler } from "agents/schedules"; - -export class ReminderAgent extends DurableObject { - readonly scheduler = new Scheduler({ - callbacks: { - sendReminder: (payload: { message: string }) => { - console.log(payload.message); - }, - }, - }); - readonly lifecycle = Lifecycle.install(this).use(this.scheduler); - - async onRequest(): Promise { - const schedule = await this.scheduler.set(60, "sendReminder", { - message: "One minute has passed", - }); - return Response.json(schedule, { status: 201 }); - } -} -``` - - - -Register callbacks in the constructor so that every wake rebuilds the same map. `set()` and `every()` check the callback name and payload type against it. Cron and interval schedules deduplicate, so you can create them in `onStart()`. - -## WebSockets - -`WebSockets` accepts WebSocket upgrades and speaks the Agents protocol, so a plain Durable Object works with `useAgent` and `AgentClient`: - - - -```ts -import { DurableObject, RpcTarget } from "cloudflare:workers"; -import { Lifecycle } from "agents/lifecycle"; -import { WebSockets } from "agents/websockets"; - -class RoomMethods extends RpcTarget { - ping(): string { - return "pong"; - } -} - -export class RoomAgent extends DurableObject { - readonly webSockets = new WebSockets({ - callables: new RoomMethods(), - handlers: { - onMessage: (connection, message) => connection.send(message), - }, - }); - readonly lifecycle = Lifecycle.install(this).use(this.webSockets); -} -``` - - - -Clients call the methods on `callables` with `agent.stub.ping()` or `agent.call("ping")`. Connections hibernate by default, so idle clients stay connected while the object leaves memory. - -## State - -`State` stores one validated value in the object's storage. Pass it to `WebSockets` to sync it with `useAgent().state`: - - - -```ts -import { DurableObject } from "cloudflare:workers"; -import { Lifecycle } from "agents/lifecycle"; -import { State } from "agents/state"; -import { WebSockets } from "agents/websockets"; - -export class CounterAgent extends DurableObject { - readonly state: State<{ count: number }> = new State({ - initialState: { count: 0 }, - onChanged: (_state, source) => this.webSockets.broadcastState(source), - }); - readonly webSockets: WebSockets = new WebSockets({ state: this.state }); - readonly lifecycle = Lifecycle.install(this) - .use(this.state) - .use(this.webSockets); -} -``` - - - -`state.get()` reads the value and `state.set(next)` validates and stores it. Pass `validateStateChange` to reject a change by throwing. - -## Queue - -`Queue` runs background callbacks one at a time, in push order, from the object's alarm: +## Capabilities by purpose - - -```ts -import { DurableObject } from "cloudflare:workers"; -import { Lifecycle } from "agents/lifecycle"; -import { Queue } from "agents/queue"; - -export class UploadAgent extends DurableObject { - readonly queue = new Queue({ - callbacks: { - processUpload: (payload: { fileName: string }) => { - console.log(`Processing ${payload.fileName}`); - }, - }, - retry: { maxAttempts: 3 }, - }); - readonly lifecycle = Lifecycle.install(this).use(this.queue); - - async onRequest(): Promise { - const item = await this.queue.push("processUpload", { - fileName: "report.pdf", - }); - return Response.json(item, { status: 202 }); - } -} -``` - - - -`push()` returns once the item is stored, without waiting for the callback. An interrupted callback can run again, so make external effects idempotent. - -## Routed agents - -`RoutedAgents` lets one hub Durable Object own many independent agents, such as one agent per chat. It stores each agent's ID and metadata, and forwards requests under `/{route}/{id}` to the right agent: - - - -```ts -import { DurableObject } from "cloudflare:workers"; -import { Lifecycle } from "agents/lifecycle"; -import { RoutedAgents } from "agents/routing"; -import type { ChatAgent } from "./chat-agent"; +### Give the agent behavior -export class UserHub extends DurableObject { - readonly chats = new RoutedAgents({ - namespace: this.env.ChatAgent, - route: "chats", - }); - readonly lifecycle = Lifecycle.install(this).use(this.chats); -} -``` +- A [harness](/agents/harnesses/), such as the [Pi harness](/agents/harnesses/pi/), runs a full agent loop with tools, retries, and recovery. +- [MCP client](/agents/capabilities/mcp-client/) connects to MCP servers and keeps the connections across restarts. +- [Routed agents](/agents/capabilities/routed-agents/) lets one hub own many independent agents, such as one per chat. +- [Browser](/agents/capabilities/browser/) keeps a persistent browser session for the model to drive. - +### Run background work -The hub can be a plain Durable Object. Each routed agent must extend the [`Agent` class](/agents/agent-class/), because deletion uses its teardown. Manage the catalog with `create()`, `list()`, `get()`, `setMetadata()`, and `delete()`. +- [Scheduler](/agents/capabilities/scheduler/) runs callbacks after a delay, at a date, on a cron, or on an interval. +- [Queue](/agents/capabilities/queue/) runs callbacks one at a time, in push order, after the request returns. -## Browser +### Connect clients -`Browser` keeps one [Browser Run](/browser-run/) session open across requests, so tabs, cookies, and logins carry over: - - - -```ts -import { DurableObject } from "cloudflare:workers"; -import { Browser, browserRun } from "agents/browser"; -import { Lifecycle } from "agents/lifecycle"; - -export class ResearchAgent extends DurableObject { - readonly browser = new Browser({ provider: browserRun(this.env.BROWSER) }); - readonly lifecycle = Lifecycle.install(this).use(this.browser); -} -``` - - - -Give the browser to a model with `browserTool()`. For more information, refer to [Browser](/agents/tools/browser/). +- [WebSockets](/agents/capabilities/websockets/) accepts hibernating connections and exposes methods to `useAgent` clients. +- [State](/agents/capabilities/state/) stores one validated value and syncs it to clients through `WebSockets`. ## Build your own harness -A harness is the loop around the model. [`PiHarness`](/agents/harnesses/pi/) is one you can install today. To write your own, combine these three capabilities: +A harness is the loop around the model. To write your own, combine three lower-level capabilities: -- `Sessions` stores the conversation. -- `Streams` stores the model's output as it arrives. -- `Tasks` runs each turn as replayable background work. +- [Sessions](/agents/capabilities/sessions/) stores the conversation. +- [Streams](/agents/capabilities/streams/) stores the model's output as it arrives, so clients can reconnect and resume. +- [Tasks](/agents/capabilities/tasks/) runs each turn as replayable steps that survive eviction. -### Sessions +If the agent runs outside the Durable Object, such as in a container, keep its sessions and operations in [`HarnessStore`](/agents/harnesses/#build-your-own-harness) instead. -`Sessions` stores conversation messages as a tree, with branches, compaction, full-text search, and attachments: +This sketch stores the user's message, runs the turn as a task, and streams the reply over Server-Sent Events (SSE): ```ts import { DurableObject } from "cloudflare:workers"; import { Lifecycle } from "agents/lifecycle"; -import { Sessions } from "agents/sessions"; - -export class ChatAgent extends DurableObject { - readonly sessions = new Sessions(); - readonly lifecycle = Lifecycle.install(this).use(this.sessions); - readonly session = this.sessions.session(); -} -``` - - - -One `Sessions` capability holds many sessions by ID. Call `session()` with no ID when the object represents a single conversation. - -### Streams +import { Sessions, type SessionMessage } from "agents/sessions"; +import { sseResponse, Streams } from "agents/streams"; +import { Tasks, type TaskStep } from "agents/tasks"; -`Streams` stores incremental output as numbered chunks. A client can reconnect, replay what it missed, and follow new chunks: +// Replace with your model call. It yields text deltas. +declare function generateReply(history: SessionMessage[]): AsyncIterable; - +type TurnInput = { streamId: string }; -```ts -import { DurableObject } from "cloudflare:workers"; -import { Lifecycle } from "agents/lifecycle"; -import { Streams } from "agents/streams"; - -export class OutputAgent extends DurableObject { +export class MyHarness extends DurableObject { + readonly sessions = new Sessions(); readonly streams = new Streams(); - readonly lifecycle = Lifecycle.install(this).use(this.streams); -} -``` - - - -Producers call `open()`, append JSON chunks, and finish with `close()` or `error()`. `sseResponse()` serves a stream as resumable Server-Sent Events (SSE). - -### Tasks - -`Tasks` runs background work as named steps. After an interruption, completed steps return their stored results and the run continues from the first unfinished step: - - - -```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: { - "publish-report@v1": async (input: { topic: string }, step: TaskStep) => { - const draft = await step.do("draft", () => `Draft about ${input.topic}`); - await step.sleep("review", "10 minutes"); - return { draft }; + "turn@v1": async ({ streamId }: TurnInput, step: TaskStep) => { + const text = await step.do("generate", async () => { + const history = await this.session.getHistory(); + const stream = await this.streams.open(streamId); + let reply = ""; + for await (const delta of generateReply(history)) { + reply += delta; + stream.append({ delta }); + } + stream.close(); + return reply; + }); + await step.do("save", async () => { + await this.session.appendMessage({ + id: streamId, + role: "assistant", + parts: [{ type: "text", text }], + }); + }); }, }, }); - readonly lifecycle = Lifecycle.install(this).use(this.tasks); + readonly lifecycle = Lifecycle.install(this) + .use(this.sessions) + .use(this.streams) + .use(this.tasks); + readonly session = this.sessions.session(); + + async onRequest(request: Request): Promise { + const url = new URL(request.url); + if (request.method === "POST") { + await this.session.appendMessage({ + id: crypto.randomUUID(), + role: "user", + parts: [{ type: "text", text: await request.text() }], + }); + const streamId = crypto.randomUUID(); + await this.tasks.run("turn@v1", { streamId }); + return Response.json({ streamId }, { status: 202 }); + } + const streamId = url.searchParams.get("stream"); + if (!streamId) return new Response("Missing stream", { status: 400 }); + return sseResponse(this.streams, streamId, { request }); + } } ``` -`tasks.run(name, input)` returns a durable receipt. A durable sleep parks the run without keeping the object in memory. +A model call cannot resume halfway. If the `generate` step is interrupted, it runs again from the start. Check `step.interrupted` in the handler to decide what to do with a partial reply. -## Capabilities on the Agent class +## Capabilities on the legacy Agent class -The [`Agent` class](/agents/agent-class/) installs `Scheduler`, `Queue`, `MCPClientManager`, `State`, `WebSockets`, and `Tasks` for you. Its methods, such as `this.schedule()`, `this.queue()`, `this.addMcpServer()`, and `this.setState()`, call those capabilities. Install any other capability on an Agent with `this.lifecycle.use()` in its constructor. +The legacy [`Agent` class](/agents/agent-class/) installs `Scheduler`, `Queue`, `MCPClientManager`, `State`, `WebSockets`, and `Tasks` for you. Its methods, such as `this.schedule()`, `this.queue()`, `this.addMcpServer()`, and `this.setState()`, call those capabilities. Install any other capability on an Agent with `this.lifecycle.use()` in its constructor. diff --git a/src/content/docs/agents/capabilities/mcp-client.mdx b/src/content/docs/agents/capabilities/mcp-client.mdx new file mode 100644 index 00000000000..8ec97b825a8 --- /dev/null +++ b/src/content/docs/agents/capabilities/mcp-client.mdx @@ -0,0 +1,166 @@ +--- +title: MCP client +description: Connect a Durable Object to MCP servers with the MCPClientManager capability, including OAuth, reconnection after restarts, and AI SDK tools. +pcx_content_type: reference +sidebar: + order: 1 +products: + - agents +--- + +import { PackageManagers, TypeScriptExample } from "~/components"; + +`MCPClientManager` from `agents/mcp/client` connects your agent to [Model Context Protocol (MCP)](/agents/model-context-protocol/) servers. It stores the servers you add in the object's SQLite database, reconnects to them when the object starts, and handles OAuth callbacks before your `onRequest()` runs. + +## Install the capability + +Install the Agents SDK and the MCP client package: + + + +Construct the manager with a client name and version, then install it on the [Lifecycle](/agents/lifecycle/): + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { MCPClientManager } from "agents/mcp/client"; + +export class ToolAgent extends DurableObject { + readonly mcp = new MCPClientManager("tool-agent", "1.0.0", { + env: this.env, + }); + readonly lifecycle = Lifecycle.install(this).use(this.mcp); +} +``` + + + +| Option | Description | +| -------------------- | --------------------------------------------------------------------------------------------------------- | +| `env` | The Worker's bindings. Required to reconnect to MCP servers reached over Durable Object RPC after a wake. | +| `createAuthProvider` | Builds the OAuth provider for a stored HTTP server. Use it to replace the default provider. | + +## Add a server + +Adding a server takes three calls. `registerServer()` stores it, `connectToServer()` opens the transport, and `discoverIfConnected()` lists its tools, prompts, and resources: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { MCPClientManager } from "agents/mcp/client"; + +export class ToolAgent extends DurableObject { + readonly mcp = new MCPClientManager("tool-agent", "1.0.0", { + env: this.env, + }); + readonly lifecycle = Lifecycle.install(this).use(this.mcp); + + async onRequest(request: Request): Promise { + const url = new URL(request.url); + if (request.method !== "POST" || !url.pathname.endsWith("/connect")) { + return Response.json({ tools: this.mcp.listTools() }); + } + + await this.mcp.registerServer("docs", { + name: "Cloudflare Docs", + url: "https://docs.mcp.cloudflare.com/mcp", + callbackUrl: `${url.origin}/agents/tool-agent/${this.lifecycle.name}/callback`, + transport: { type: "auto" }, + }); + + const connection = await this.mcp.connectToServer("docs"); + if (connection.state === "authenticating") { + return Response.redirect(connection.authUrl); + } + if (connection.state === "connected") { + await this.mcp.discoverIfConnected("docs"); + } + return Response.json({ state: connection.state }); + } +} +``` + + + +`connectToServer()` returns one of these states: + +| State | Meaning | +| ------------------ | -------------------------------------------------------------------- | +| `"connected"` | The transport is open. Call `discoverIfConnected()` to load tools. | +| `"authenticating"` | The server needs OAuth. Send the user to `authUrl`. | +| `"failed"` | The connection failed. The result includes an `error` message. | + +The server stays registered after the object leaves memory. On the next start, the manager reconnects to every stored server. + +## Handle OAuth + +When a server needs OAuth, the manager sends the user back to the `callbackUrl` you registered. Route that URL to the same named object. The manager matches the callback by origin and path, completes the token exchange, and responds before your `onRequest()` runs. + +To control the response the user sees after authorizing, call `configureOAuthCallback()` in `onStart()`: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { MCPClientManager } from "agents/mcp/client"; + +export class ToolAgent extends DurableObject { + readonly mcp = new MCPClientManager("tool-agent", "1.0.0"); + readonly lifecycle = Lifecycle.install(this).use(this.mcp); + + onStart(): void { + this.mcp.configureOAuthCallback({ + successRedirect: "/settings/integrations", + errorRedirect: "/settings/integrations?error=mcp", + }); + } +} +``` + + + +`configureOAuthCallback()` also accepts a `customHandler` that returns your own `Response`, such as a page that closes an OAuth popup. + +## Use tools + +| Method | Returns | +| --------------------------------------- | --------------------------------------------------------------- | +| `listTools(filter?)` | The raw MCP tools from every ready server | +| `getAITools(filter?)` | The same tools as an AI SDK tool set, ready for `streamText()` | +| `callTool({ serverId, name, arguments })` | The result of one tool call | +| `listPrompts()`, `listResources()` | Prompts and resources from every ready server | + +Pass a `filter` such as `{ serverId: "docs" }` to limit the results to some servers. + +The manager reconnects in the background after a wake. Call `waitForConnections()` before you read tools, so that servers that are still reconnecting are included: + +```ts +await this.mcp.waitForConnections({ timeout: 10_000 }); +const tools = this.mcp.getAITools(); +``` + +## Manage servers + +- `listServers()` returns every stored server. +- `removeServer(id)` closes the connection and deletes the server. +- `mcpConnections[id].connectionState` reports the live state of one server. + +## Call the manager over RPC + +RPC methods do not start the Lifecycle. Call `await this.lifecycle.start()` before you read tools or servers in an RPC method: + +```ts +async tools() { + await this.lifecycle.start(); + return this.mcp.listTools(); +} +``` + +## On the legacy Agent class + +The [`Agent` class](/agents/agent-class/) installs `MCPClientManager` as `this.mcp`. Its `addMcpServer()` method runs the register, connect, and discover steps in one call, and builds the callback URL for you. For more information, refer to [McpClient](/agents/model-context-protocol/apis/client-api/). diff --git a/src/content/docs/agents/capabilities/queue.mdx b/src/content/docs/agents/capabilities/queue.mdx new file mode 100644 index 00000000000..aff5e232d7b --- /dev/null +++ b/src/content/docs/agents/capabilities/queue.mdx @@ -0,0 +1,124 @@ +--- +title: Queue +description: Run durable background callbacks one at a time, in push order, from a Durable Object's alarm with the Queue capability. +pcx_content_type: reference +sidebar: + order: 5 + badge: Experimental +products: + - agents +--- + +import { TypeScriptExample } from "~/components"; + +:::caution[Experimental] +The `Queue` capability may change between releases. +::: + +`Queue` from `agents/queue` runs background work after the current request returns. Each item stores a callback name and a payload, runs from the object's alarm one at a time in push order, retries when the callback throws, and survives the object leaving memory. + +`Queue` is not [Cloudflare Queues](/queues/). It needs no binding, and items stay inside one Durable Object. + +## Install the capability + +Register callbacks by name in the constructor, then install the queue on the [Lifecycle](/agents/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: 3, baseDelayMs: 100, maxDelayMs: 1_000 }, + }); + readonly lifecycle = Lifecycle.install(this).use(this.queue); + + async onRequest(request: Request): Promise { + const body = await request.json(); + const item = await this.queue.push("processUpload", body); + return Response.json(item, { status: 202 }); + } +} +``` + + + +`push()` returns once the item is stored. The callback runs from the next alarm, in a fresh invocation, so the request does not wait for it. The callback does not see the request or connection that pushed the item. + +## Options + +| Option | Description | +| ----------- | --------------------------------------------------------------------------------------------------------------------- | +| `callbacks` | Named callbacks `(payload, item) => unknown`. Register them in the field initializer so every wake has the same names | +| `retry` | Default retry policy. Defaults to `{ maxAttempts: 3, baseDelayMs: 100, maxDelayMs: 3000 }` | +| `onError` | Observes an item's failure after its last attempt | + +## Push items + +```ts +await this.queue.push("processUpload", payload, { + id: `upload:${payload.fileName}`, + retry: { maxAttempts: 5 }, +}); +``` + +- `id` sets a stable ID. Pushing an existing ID replaces that item and keeps its place in the queue. Use it when the same work might be queued twice. +- `retry` overrides the queue's retry policy for this item. + +Once the Lifecycle has started, `push()` writes the item before its promise resolves. A push in the same synchronous block as your own SQL writes commits with them. + +## Manage items + +| Method | Description | +| ---------------------- | ---------------------------------------------------------------------- | +| `get(id)` | Returns one pending item, or `undefined` | +| `list(criteria?)` | Returns pending items in push order. Filter 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: + +```ts +type QueueItem = { + id: string; + callback: string; + payload: T; + createdAt: number; // Unix timestamp in seconds + retry?: RetryOptions; +}; +``` + +## How items run + +1. `push()` stores the item as a job in the [Lifecycle job queue](/agents/lifecycle/job-queue/), due now. +2. The alarm runs pending items in push order, awaiting each callback before the next. +3. A callback that throws retries with exponential backoff, up to its `maxAttempts`. +4. After the last attempt, the item is dropped, `onError` runs, and the queue publishes a `queue:error` event. +5. If the isolate dies mid-callback, the Lifecycle backup alarm wakes the object and the item runs again. + +Callbacks can run more than once, so make their external effects idempotent. Keep each callback short, because a slow callback delays every other job on the object. For multi-step work that needs to resume where it stopped, use [`Tasks`](/agents/capabilities/tasks/). + +## Limits + +- Items run one at a time, not in parallel. +- There is no priority. Items run in push order. +- Failed items are dropped after their last attempt. There is no dead-letter queue. + +## Observability + +The queue publishes `queue:create`, `queue:retry`, and `queue:error` events to the `agents:*` [diagnostics channels](/agents/platform/observability/diagnostics-channels/). + +## On the legacy 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. For more information, refer to [Queue tasks](/agents/agent-class/queue-tasks/). diff --git a/src/content/docs/agents/capabilities/routed-agents.mdx b/src/content/docs/agents/capabilities/routed-agents.mdx new file mode 100644 index 00000000000..76e3938c8fe --- /dev/null +++ b/src/content/docs/agents/capabilities/routed-agents.mdx @@ -0,0 +1,119 @@ +--- +title: Routed agents +description: Run one agent per chat, document, or task behind a hub Durable Object with the RoutedAgents capability, which keeps a catalog and forwards requests and WebSockets. +pcx_content_type: reference +sidebar: + order: 2 + badge: Experimental +products: + - agents +--- + +import { TypeScriptExample } from "~/components"; + +:::caution[Experimental] +The `RoutedAgents` capability may change between releases. +::: + +Many applications give each user an open-ended set of agents: one per chat, document, or task. `RoutedAgents` from `agents/routing` lets one hub Durable Object own that set. The hub keeps a catalog of entry IDs and metadata in its own database, and forwards requests and WebSocket upgrades under one path segment to the right agent. + +Each routed agent is an independent top-level Durable Object, with its own storage, alarm, and placement. Listing, searching, and deleting entries only touch the hub, so they never wake the routed agents. + +## Install the capability + +The hub can be a plain Durable Object. The routed agents must extend the [`Agent` class](/agents/agent-class/), because deletion uses its teardown. + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Agent, routeAgentRequest } from "agents"; +import { Lifecycle } from "agents/lifecycle"; +import { RoutedAgents } from "agents/routing"; + +type ChatMetadata = { title: string }; + +export class ChatAgent extends Agent { + // One agent per chat: its own storage, alarms, and connections. +} + +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 entry = await this.chats.create({ metadata: { title: "New chat" } }); + return Response.json(entry, { status: 201 }); + } + return Response.json(await this.chats.list()); + } +} + +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 the capability claims, such as `"chats"` | + +Install `RoutedAgents` before `WebSockets`, so that upgrades under the route reach the routed agent and only the hub's own upgrades reach `WebSockets`. + +## Route requests + +Requests under `/{route}/{id}` reach the routed agent behind that entry. The rest of the path is kept: + +```txt +/agents/user-hub/alice -> UserHub "alice" +/agents/user-hub/alice/chats/{id} -> the ChatAgent behind entry {id} +/agents/user-hub/alice/chats/{id}/... -> the same ChatAgent, with the suffix +``` + +A forwarded WebSocket upgrade is answered by the routed agent, which then owns the socket. Its messages never 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 elsewhere in the hub's paths. Forwarding matches the segment anywhere in the path. + +## 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, or `null` for an unknown or deleted entry | +| `list()` | Returns active entries, most recently updated first | +| `setMetadata(id, metadata)` | Replaces an entry's metadata. Returns `false` for an unknown entry | +| `delete(id)` | Hides the entry, tells the routed agent to delete its storage, then removes the row | + +Clients only see entry IDs. The hub maps each entry to a random Durable Object name that never leaves the hub. + +The catalog holds existence, ownership, and metadata. Conversation data stays in the routed agent. To keep listing and search cheap, have the routed agent push a summary back to the hub with `setMetadata()` through an RPC method on the hub. + +## Delete entries + +`delete(id)` is safe to retry. It hides the entry first, then tells the routed agent to delete itself. The routed agent wipes its storage the next time it wakes. If the call fails partway, the entry stays hidden and a second `delete(id)` finishes the job. + +Before you delete the hub itself, call `await this.lifecycle.dispose()`. Disposal tells every remaining routed agent to delete itself, retrying each one a few times. Then delete the hub's storage with `this.ctx.storage.deleteAll()`. A routed agent that is still unreachable after those retries is left behind, with no catalog row to retry from. An `Agent` hub does this for you in `this.destroy()`. + +## Related resources + +- 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` for the hub's own methods. +- [Routing on the Agent class](/agents/agent-class/routing/) covers `routeAgentRequest()` options, custom paths, and `getAgentByName()`. diff --git a/src/content/docs/agents/capabilities/scheduler.mdx b/src/content/docs/agents/capabilities/scheduler.mdx new file mode 100644 index 00000000000..1e0661125a6 --- /dev/null +++ b/src/content/docs/agents/capabilities/scheduler.mdx @@ -0,0 +1,125 @@ +--- +title: Scheduler +description: Run callbacks on a Durable Object after a delay, at a date, from a cron expression, or on an interval with the Scheduler capability. +pcx_content_type: reference +sidebar: + order: 4 +products: + - agents +--- + +import { TypeScriptExample } from "~/components"; + +`Scheduler` from `agents/schedules` runs registered callbacks in the future: after a delay, at a date, from a cron expression, or at a fixed interval. Schedules are stored as jobs in the [Lifecycle job queue](/agents/lifecycle/job-queue/), so they survive eviction and share the object's alarm with other capabilities. + +## Install the capability + +Register callbacks by name in the constructor, then install the scheduler on the [Lifecycle](/agents/lifecycle/): + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { Scheduler, type Schedule } from "agents/schedules"; + +type Reminder = { message: string }; + +export class ReminderAgent extends DurableObject { + readonly scheduler = new Scheduler({ + callbacks: { + sendReminder: (payload: Reminder, schedule: Schedule) => { + console.log(`Reminder ${schedule.id}: ${payload.message}`); + }, + }, + }); + readonly lifecycle = Lifecycle.install(this).use(this.scheduler); + + async onRequest(): Promise { + const schedule = await this.scheduler.set(300, "sendReminder", { + message: "Five minutes have passed", + }); + return Response.json(schedule, { status: 201 }); + } +} +``` + + + +Each schedule stores the callback's name, not the function. The callbacks map is rebuilt every time the object starts, so a schedule created before an eviction finds its callback after the object wakes. Register every callback unconditionally in the field initializer. + +`set()` and `every()` check the callback name and payload type against the map. A callback receives the payload and the schedule that fired. It runs inside the host's invocation context, so [`getCurrentAgent()`](/agents/lifecycle/get-current-agent/) returns the object. + +## Schedule work + +`set()` takes the time as its first argument. The type of that argument picks the mode: + +| `when` | Mode | Example | +| ----------------------- | --------- | ------------------------------------------------- | +| `number` | Delayed | `set(60, "sendReminder", payload)` runs in 60 seconds | +| `Date` | Scheduled | `set(new Date("2026-12-25T09:00:00Z"), ...)` | +| `string` | Cron | `set("0 8 * * 1-5", ...)` runs at 08:00 UTC on weekdays | + +`every()` creates a fixed-interval schedule: + +```ts +await this.scheduler.every(30, "sendReminder", { message: "Every 30 seconds" }); +``` + +The first interval run happens after `intervalSeconds`, not immediately. If a run is still going when the next one is due, the scheduler skips it. + +### Deduplicate schedules + +Cron and interval schedules are idempotent by default. Calling `set()` or `every()` again with the same callback, timing, and payload returns the existing schedule. You can create recurring schedules in `onStart()` without adding a row on every wake: + +```ts +async onStart(): Promise { + await this.scheduler.set("0 * * * *", "sendReminder", { message: "Hourly" }); +} +``` + +Delayed and dated schedules create a new row on every call. Pass `{ idempotent: true }` as the fourth argument to deduplicate them on callback and payload. + +## Manage schedules + +| Method | Description | +| ------------------- | ---------------------------------------------------------------- | +| `get(id)` | Returns one schedule, or `undefined` | +| `list(criteria?)` | Returns schedules, filtered by `id`, `type`, or `timeRange` | +| `cancel(id)` | Removes a schedule. Returns `true` if one was removed | + +```ts +const crons = await this.scheduler.list({ type: "cron" }); +for (const schedule of crons) { + await this.scheduler.cancel(schedule.id); +} +``` + +A one-time schedule is deleted after it runs. A cron or interval schedule moves to its next run time. + +## Options + +| Option | Description | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------- | +| `callbacks` | Named callbacks `(payload, schedule) => unknown` | +| `retry` | Default retry policy for callbacks. Defaults to three attempts, a 100 ms base delay, and a 3,000 ms maximum delay | +| `hungScheduleTimeoutSeconds` | Seconds before a running interval counts as abandoned. Defaults to `30` | +| `onError` | Observes a callback's failure after its last retry | + +Pass `{ retry }` as the fourth argument to `set()` or `every()` to override the retry policy for one schedule. + +A callback that throws on every attempt is reported to `onError`. A one-time schedule is then removed, and a recurring schedule continues at its next run. + +## Limits + +- Cron schedules have minute precision. Interval schedules have second precision. +- Callbacks run one at a time with every other job on the object. Keep them short, and move long work to [`Tasks`](/agents/capabilities/tasks/) or [`Queue`](/agents/capabilities/queue/). +- Payloads must be JSON-serializable. + +## Observability + +The scheduler publishes `schedule:create`, `schedule:execute`, `schedule:retry`, `schedule:error`, and `schedule:cancel` events to the `agents:schedule` [diagnostics channel](/agents/platform/observability/diagnostics-channels/). + +## On the legacy Agent class + +The [`Agent` class](/agents/agent-class/) installs a `Scheduler` as `this.scheduler`. Its `this.schedule()`, `this.scheduleEvery()`, `this.listSchedules()`, and `this.cancelSchedule()` methods call it, and callback names resolve to methods on the Agent. For more information, refer to [Schedule tasks](/agents/agent-class/schedule-tasks/). diff --git a/src/content/docs/agents/capabilities/sessions.mdx b/src/content/docs/agents/capabilities/sessions.mdx index 5bd33e614bf..aeb9f3a7dc6 100644 --- a/src/content/docs/agents/capabilities/sessions.mdx +++ b/src/content/docs/agents/capabilities/sessions.mdx @@ -1,910 +1,184 @@ --- title: Sessions +description: Store conversation history in a Durable Object with the Sessions capability, including branches, streamed reads, compaction, attachments, and full-text search. pcx_content_type: reference -description: Persistent conversation storage with tree-structured messages, context blocks, compaction, full-text search, and AI-controllable tools. -tags: - - AI sidebar: - order: 18 + order: 8 + badge: Experimental products: - agents --- -import { - InlineBadge, - LinkCard, - PackageManagers, - TypeScriptExample, - WranglerConfig, -} from "~/components"; - -The Session API provides persistent conversation storage for agents, with tree-structured messages (inspired by [Pi](https://pi.dev)), context blocks, compaction, full-text search, and AI-controllable tools. By default, it uses Durable Object SQLite. External Postgres storage is also available for apps that need shared database access, analytics, or cross-Durable Object queries. +import { TypeScriptExample } from "~/components"; :::caution[Experimental] - -The Session API is under `agents/experimental/memory/session`. The API surface is stable but may evolve before graduating to the main package. - +Everything exported from `agents/sessions` may change between releases. ::: -## Quick start - - - -```ts -import { Agent } from "agents"; -import { Session } from "agents/experimental/memory/session"; - -class MyAgent extends Agent { - session = Session.create(this) - .withContext("soul", { - provider: { get: async () => "You are a helpful assistant." }, - }) - .withContext("memory", { - description: "Learned facts about the user", - maxTokens: 1100, - }) - .withCachedPrompt(); - - async onMessage(message: unknown) { - await this.session.appendMessage(message); - const history = await this.session.getHistory(); - const system = await this.session.freezeSystemPrompt(); - const tools = await this.session.tools(); - // Pass history, system prompt, and tools to your LLM - } -} -``` - - - -## Creating a session - -### Builder API (recommended) - -Use `Session.create(agent)` with a chainable builder. Context providers without an explicit `provider` option are auto-wired to SQLite. - - - -```ts -const session = Session.create(this) - .withContext("soul", { provider: { get: async () => "You are helpful." } }) - .withContext("memory", { description: "Learned facts", maxTokens: 1100 }) - .withCachedPrompt() - .onCompaction(myCompactFn) - .compactAfter(100_000); -``` - - - -### Direct constructor - -For full control over providers: - - - -```ts -import { - Session, - AgentSessionProvider, - AgentContextProvider, -} from "agents/experimental/memory/session"; - -const session = new Session(new AgentSessionProvider(this), { - context: [ - { - label: "memory", - description: "Notes", - maxTokens: 500, - provider: new AgentContextProvider(this, "memory"), - }, - { label: "soul", provider: { get: async () => "You are helpful." } }, - ], -}); -``` - - - -### Builder methods - -All builder methods return `this` for chaining. Order does not matter — providers are resolved lazily on first use. - -| Method | Description | -| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `Session.create(agent)` | Static factory. `agent` is any object with a `sql` tagged template method (your Agent or Durable Object). | -| `.forSession(sessionId)` | Namespace this session by ID. Required for multi-session isolation when not using `SessionManager`. | -| `.withContext(label, options?)` | Add a context block. Refer to [Context blocks](#context-blocks). | -| `.withCachedPrompt(provider?)` | Enable system prompt persistence. The prompt is frozen on first use and survives hibernation and eviction. | -| `.onCompaction(fn)` | Register a compaction function. Refer to [Compaction](#compaction). | -| `.compactAfter(tokenThreshold, options?)` | Auto-compact when estimated token count exceeds the threshold. Requires `.onCompaction()`. Pass `{ tokenCounter }` to control how the threshold is measured. | -| `.onCompactionError(handler)` | Handle errors from automatic compaction. Handler failures are swallowed so message writes remain non-fatal. | - -## Messages - -Messages use the `SessionMessage` type — a minimal shape with `id`, `role`, `parts`, and optional `createdAt`. The AI SDK's `UIMessage` is structurally compatible and can be passed directly. The session stores messages in a tree structure via `parent_id`, enabling branching conversations. - - - -```ts -// Append — auto-parents to the latest leaf unless parentId is specified -await session.appendMessage(message); -await session.appendMessage(message, parentId); - -// Update an existing message (matched by message.id) -await session.updateMessage(message); - -// Delete specific messages -await session.deleteMessages(["msg-1", "msg-2"]); - -// Clear all messages and skill state -await session.clearMessages(); -``` - - - -:::note - -Session methods are async. SQLite-backed sessions are usually fast, but external providers may perform network I/O, and `appendMessage()` may also trigger auto-compaction. - -::: - -### Reading history - - - -```ts -// Linear history from root to the latest leaf -const messages = await session.getHistory(); - -// History to a specific leaf (for branching) -const branch = await session.getHistory(leafId); - -// Get a single message -const msg = await session.getMessage("msg-1"); - -// Get the newest message -const latest = await session.getLatestLeaf(); - -// Count messages in path -const count = await session.getPathLength(); -``` - - - -### Branching - -Messages form a tree. When you `appendMessage` with a `parentId` that already has children, you create a branch. Use `getBranches()` to get all child messages branching from a given point: - - - -```ts -// Get all child messages that branch from messageId -const branches = await session.getBranches(messageId); -``` - - - -This powers features like response regeneration — pass the user message ID to get both the original and regenerated responses. `getHistory(leafId)` walks the chosen path. - -## Search - -Full-text search over the conversation history using SQLite FTS5: +`Sessions` from `agents/sessions` stores conversation history in the object's SQLite database. Messages form a tree, so a conversation can branch. You can read history as a stream or in byte-budgeted windows, replace old ranges with summaries, store image and file attachments, and search the text. - - -```ts -const results = await session.search("deployment Friday", { limit: 10 }); -// Returns: Array<{ id, role, content, createdAt? }> -``` - - - -SQLite-backed sessions use FTS5 with porter stemming and unicode tokenization. Postgres-backed sessions use the provider's Postgres full-text index. `search()` throws if the session provider does not support search. - -## Context blocks - -Context blocks are persistent key-value sections injected into the system prompt. Each block has a **label**, optional **description**, and a **provider** that determines its behavior. - -### Provider types +`Sessions` stores messages only. [Project Think](/agents/harnesses/think/) and `AIChatAgent` use it to store their messages. -There are four provider types, detected by duck-typing: +Earlier releases also put context blocks, cached system prompts, and memory tools on the session. Those moved to `agents/context`. For more information, refer to the [Context documentation](https://github.com/cloudflare/agents/blob/main/docs/agents/context.md). -| Provider | Interface | Behavior | AI tool | -| --------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------- | -| **ContextProvider** | `get()` | Read-only block in system prompt | — | -| **WritableContextProvider** | `get()` + `set()` | Writable via AI | `set_context` | -| **SkillProvider** | `get()` + `load()` + `set?()` | On-demand keyed documents. `get()` returns a metadata listing; `load(key)` fetches full content. | `load_context`, `unload_context`, `set_context` | -| **SearchProvider** | `get()` + `search()` + `set?()` | Full-text searchable entries. `get()` returns a summary; `search(query)` runs FTS5. | `search_context`, `set_context` | - -### Built-in providers - -**`AgentContextProvider`** — SQLite-backed writable context. This is the default when using the builder without an explicit provider. +## Install the capability ```ts -import { AgentContextProvider } from "agents/experimental/memory/session"; - -new AgentContextProvider(this, "memory"); -``` - - - -**`R2SkillProvider`** — Cloudflare R2 bucket for on-demand document loading. Skills are listed in the system prompt as metadata; the model loads full content on demand via `load_context`. +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { Sessions } from "agents/sessions"; - - -```ts -import { R2SkillProvider } from "agents/experimental/memory/session"; - -Session.create(this).withContext("skills", { - provider: new R2SkillProvider(env.SKILLS_BUCKET, { prefix: "skills/" }), -}); +export class ChatAgent extends DurableObject { + readonly sessions = new Sessions(); + readonly lifecycle = Lifecycle.install(this).use(this.sessions); + readonly session = this.sessions.session(); +} ``` -**`AgentSearchProvider`** — SQLite FTS5 searchable context. Entries are indexed and searchable by the model via `search_context`. - - - -```ts -import { AgentSearchProvider } from "agents/experimental/memory/session"; +`sessions.session()` with no ID returns the default session. Use it when one Durable Object holds one conversation. Pass an ID, such as `sessions.session("drafts")`, to keep several sessions in one object. -Session.create(this).withContext("knowledge", { - description: "Searchable knowledge base", - provider: new AgentSearchProvider(this), -}); -``` +For user-facing chat, prefer one Durable Object per conversation. Each conversation then has its own storage and hibernates on its own. To list a user's conversations, keep a directory in a hub object with [`RoutedAgents`](/agents/capabilities/routed-agents/). - +`Sessions` needs no alarm. -### Adding and removing context at runtime +## Write messages -Blocks can be added and removed dynamically after initialization: - - +A `SessionMessage` has an `id`, a `role`, and `parts`. The AI SDK `UIMessage` type is compatible with it: ```ts -// Add a new block (auto-wires to SQLite if no provider given) -await session.addContext("extension-notes", { - description: "From extension X", - maxTokens: 500, +const result = await this.session.appendMessage({ + id: crypto.randomUUID(), + role: "user", + parts: [{ type: "text", text: "Hello" }], }); -// Remove it -session.removeContext("extension-notes"); - -// Rebuild the system prompt to reflect changes -await session.refreshSystemPrompt(); +result.inserted; // false when a message with this ID already existed +result.message; // the stored form ``` - - -:::note +An append without `parentId` attaches to the newest message. Pass `parentId: null` to start a new root, or a message ID to start a branch from that message. -`addContext` and `removeContext` do not automatically update the frozen system prompt. Call `refreshSystemPrompt()` afterward. - -::: - -### Reading and writing context - - +Mark messages that come from a client with `source: "client"`. That strips the metadata keys you reserve: ```ts -// Read a single block -const block = session.getContextBlock("memory"); -// { label, description?, content, tokens, maxTokens?, writable, isSkill, isSearchable } - -// Read all blocks -const blocks = session.getContextBlocks(); - -// Replace content entirely -await session.replaceContextBlock("memory", "User likes coffee."); - -// Append content -await session.appendContextBlock("memory", "\nUser prefers dark roast."); +const sessions = new Sessions({ reservedMetadataKeys: ["channel"] }); +await sessions.session().appendMessage(clientMessage, { source: "client" }); ``` - - -### System prompt +`source: "client"` does not limit message size. `Sessions` stores messages of any size by splitting them across rows. Check the size of untrusted input before you append it. -The system prompt is built from all context blocks with headers and metadata: +Other write methods: -```txt -══════════════════════════════════════════════ -SOUL (Identity) [readonly] -══════════════════════════════════════════════ -You are a helpful assistant. +| Method | Description | +| ------------------------- | ------------------------------------------------------------------------ | +| `updateMessage(message)` | Replaces a stored message. Returns `null` if the ID is not in the session | +| `upsertMessage(message)` | Updates the message, or appends it when it does not exist | +| `deleteMessages(ids)` | Deletes messages. Their children attach to the deleted message's parent | +| `clearMessages()` | Deletes every message in the session | +| `importMessage(message, { parentId, createdAt })` | Writes one historical message as is, for migrations | -══════════════════════════════════════════════ -MEMORY (Learned facts) [45% — 495/1100 tokens] -══════════════════════════════════════════════ -User likes coffee. -User prefers dark roast. -``` +## Read history - - -```ts -// Freeze — first call renders and persists; subsequent calls return cached value -const prompt = await session.freezeSystemPrompt(); - -// Refresh — re-render from current block state and persist -const updated = await session.refreshSystemPrompt(); -``` - - - -The frozen prompt survives Durable Object hibernation and eviction when `withCachedPrompt()` is enabled. - -## AI tools - -Session automatically generates tools based on the provider types of your context blocks. Pass these to your LLM alongside your own tools. - - +Read long histories as a stream. The iterator fetches content in bounded windows and does not keep earlier messages in memory: ```ts -const tools = await session.tools(); -const allTools = { ...tools, ...myTools }; +for await (const message of this.session.history()) { + console.log(message.id); +} ``` - - -### set_context - -Generated when any writable block exists. Writes to regular blocks, skill blocks (keyed), or search blocks (keyed). Enforces `maxTokens` limits. - -### load_context - -Generated when any skill block exists. Loads full content by key from a `SkillProvider`. - -### unload_context - -Generated alongside `load_context`. Frees context space by unloading a previously loaded skill. The skill remains available for re-loading. - -### search_context - -Generated when any search block exists. Full-text search within a searchable context block. Returns top 10 results by FTS5 rank. +Pass `{ newestFirst: true }` to start from the newest message. Breaking out of the loop early leaves older rows unread. -### session_search +| Method | Use it for | +| -------------------------------------- | --------------------------------------------------------------------------- | +| `history(options?)` | Streaming every message on the current branch | +| `historyBatches({ batchSize, maxBatchBytes })` | Sending history in fixed-cost batches | +| `getRecentHistory(maxBytes)` | Loading the most recent messages that fit in a byte budget | +| `getHistory(options?)` | An array of the whole branch. Avoid it for unbounded transcripts | +| `getMessage(id)`, `getLatestLeaf()` | Reading one message | +| `getBranches(parentId)` | Listing the alternative replies to one message | -Available on `SessionManager` only. Searches across all sessions. +Pass `{ leafId }` to `history()` or `getHistory()` to read a different branch. -## Compaction +## Compact history -Compaction summarizes older messages to keep conversations within token limits. Original messages are preserved in SQLite — the summary is a non-destructive overlay applied at read time. - -### Setup - - +Compaction replaces a range of messages with a summary when you read history. The original rows stay in storage: ```ts -import { createCompactFunction } from "agents/experimental/memory/utils/compaction-helpers"; +import { createCompactFunction } from "agents/sessions"; -const session = Session.create(this) - .withContext("memory", { maxTokens: 1100 }) +this.session .onCompaction( createCompactFunction({ - summarize: (prompt) => - generateText({ model: myModel, prompt }).then((r) => r.text), - protectHead: 3, - tailTokenBudget: 20000, - minTailMessages: 2, - tokenCounter: async (messages) => estimateWithYourTokenizer({ messages }), + summarize: async (prompt) => summarizeWithModel(prompt), + keepRecentTokens: 20_000, }), ) - .compactAfter(100_000); -``` - - - -### How compaction works - -1. **Protect head** — first N messages are never compacted (default 3) -2. **Protect tail** — walk backward from the end, accumulating tokens up to a budget (default 20K tokens) -3. **Align boundaries** — shift boundaries to avoid splitting tool call/result pairs -4. **Summarize middle** — send the middle section to an LLM with a structured format (Topic, Key Points, Current State, Open Items) -5. **Store overlay** — saved in the `assistant_compactions` table, keyed by `fromMessageId` and `toMessageId` -6. **Iterative** — on subsequent compactions, the existing summary is passed to the LLM to update rather than replace - -When `getHistory()` is called, compaction overlays are applied transparently — the compacted range is replaced by a synthetic summary message. - -### Manual compaction - - - -```ts -const result = await session.compact(); - -// Or manage overlays directly -await session.addCompaction("Summary of messages 1-50", "msg-1", "msg-50"); -const overlays = await session.getCompactions(); + .compactAfter(80_000); ``` - - -### Auto-compaction - -When `.compactAfter(threshold)` is set, `appendMessage()` checks the estimated token count after each write. If it exceeds the threshold, `compact()` is called automatically. Auto-compaction failure is non-fatal — the message is already saved. - -:::note -Auto-compaction is checked **between turns** (on each `appendMessage()`), not within a turn. A single long, tool-heavy turn can grow past the model context window mid-flight, before the next check. `@cloudflare/think` adds opt-in mid-turn recovery on top of this — refer to [Context-window overflow recovery](/agents/harnesses/think/recovery/#context-window-overflow-recovery). -::: - -By default, the estimate includes stored message parts plus the Session-managed frozen system prompt, so context blocks and cached prompts managed by `Session` contribute to the threshold. It does not include framework-specific prompt additions or tool schema serialization that happen outside `Session`. - -There are two token-counting decisions: - -- `.compactAfter(threshold, { tokenCounter })` controls **when** automatic compaction is triggered after writes. -- `createCompactFunction({ tokenCounter })` controls **which** tail messages are protected from summarization. Use this when tool-heavy histories are much larger than the Workers-safe heuristic can estimate. - -You usually only need to configure one counter. The `.compactAfter()` counter also flows into `createCompactFunction`'s boundary walk (via `CompactContext`) when no explicit `createCompactFunction({ tokenCounter })` is given, so a single counter drives both "should we compact?" and "what should we compact?". +`compactAfter()` runs compaction once the session's estimated token count passes the limit. `Sessions` stamps a token estimate on each message when it is written, so the check does not read the transcript. You can also call `compact()` yourself, or store a summary you produced with `addCompaction(summary, fromMessageId, toMessageId)`. -:::caution -The flowed counter is invoked **per message** during the boundary walk. A tokenizer-style counter budgets accurately; a usage-only counter that returns a fixed whole-prompt total (for example `usage.inputTokens` regardless of which messages are passed) degrades the tail budget to `minTailMessages` — compaction still runs and context stays bounded, but the byte budget is effectively ignored. Pass an explicit per-message `createCompactFunction({ tokenCounter })` for precise tail budgeting. -::: +## Attachments and large messages -Use a custom counter when you have model-reported usage or your own tokenizer: +A message part with a non-text media type and inline data, such as an image or a PDF, is stored separately from the message row. Reads put the data back, so the message you read is identical to the message you wrote. Identical payloads are stored once, and a payload is deleted when the last message that uses it is deleted. - +A message larger than one row is split across extra rows and joined again on read. Nothing is truncated. -```ts -const session = Session.create(this) - .onCompaction(myCompactFn) - .compactAfter(100_000, { - tokenCounter: async ({ messages, systemPrompt, contextBlocks }) => { - return estimateWithYourTokenizer({ - messages, - systemPrompt, - contextBlocks, - }); - }, - }) - .onCompactionError((err) => { - console.warn("Auto-compaction failed", err); - }); -``` +Attachments and large messages still count against the Durable Object's storage limit. Keep files in [R2](/r2/) and put a reference in the message instead. - - -:::note - -The default token estimation is heuristic (not tiktoken). It uses `max(chars/4, words*1.3)` with 4 tokens per-message overhead, and also applies the string heuristic to the Session-managed system prompt. Tiktoken would add 80–120 MB heap overhead, which exceeds Cloudflare Workers' 128 MB limit. - -::: - -## SessionManager - -`SessionManager` is a registry for multiple named sessions within a single Durable Object. It provides lifecycle management, convenience methods, and cross-session search. - -### Creating a SessionManager - - +## Search ```ts -import { SessionManager } from "agents/experimental/memory/session"; - -const manager = SessionManager.create(this) - .withContext("soul", { provider: { get: async () => "You are helpful." } }) - .withContext("memory", { description: "Learned facts", maxTokens: 1100 }) - .withCachedPrompt() - .onCompaction(myCompactFn) - .compactAfter(100_000) - .withSearchableHistory("history"); +const results = await this.session.search("deployment failed"); ``` - - -Context blocks, prompt caching, and compaction settings are propagated to all sessions created through the manager. Provider keys are automatically namespaced by session ID. - -### Builder methods +The first `search()` call builds a full-text index over the stored text parts. After that, every write keeps the index up to date. An object that never searches never pays for the index. -| Method | Description | -| ----------------------------------------- | ------------------------------------------------------------------------------------------------------- | -| `SessionManager.create(agent)` | Static factory. | -| `.withContext(label, options?)` | Add context block template for all sessions. | -| `.withCachedPrompt(provider?)` | Enable prompt persistence for all sessions. | -| `.onCompaction(fn)` | Register compaction function for all sessions. | -| `.compactAfter(tokenThreshold, options?)` | Auto-compact threshold for all sessions. Supports the same `tokenCounter` option as `Session`. | -| `.onCompactionError(handler)` | Handle automatic compaction errors for managed sessions. | -| `.withSearchableHistory(label)` | Add a cross-session searchable history block. The model can search past conversations from any session. | +## Watch for changes -### Session lifecycle - - +`sessions.subscribe()` reports each write in the current isolate. `session.mirror()` keeps an in-memory array in sync with one session: ```ts -// Create a new session -const info = await manager.create("My Chat"); - -// Create with metadata -const info2 = await manager.create("My Chat", { - parentSessionId: "parent-id", - model: "claude-sonnet-4-20250514", - source: "web", +const stop = this.session.mirror({ + get: () => this.messages, + set: (messages) => { + this.messages = messages; + }, }); - -// Get session metadata (null if not found) -const session = await manager.get(sessionId); - -// List all sessions (ordered by updated_at DESC) -const sessions = await manager.list(); - -// Rename -await manager.rename(sessionId, "New Name"); - -// Delete (clears messages too) -await manager.delete(sessionId); -``` - - - -### Accessing sessions - - - -```ts -// Get or create the Session instance for an ID -// Lazy — creates on first access, caches for subsequent calls -const session = manager.getSession(sessionId); -``` - - - -### Message convenience methods - -These delegate to the underlying Session and update the session's `updated_at` timestamp: - - - -```ts -// Append a single message -await manager.append(sessionId, message, parentId); - -// Add or update (upsert) -await manager.upsert(sessionId, message, parentId); - -// Batch append (auto-chains parent IDs) -await manager.appendAll(sessionId, messages, parentId); - -// Read history -const history = await manager.getHistory(sessionId, leafId); - -// Message count -const count = await manager.getMessageCount(sessionId); - -// Clear messages -await manager.clearMessages(sessionId); - -// Delete specific messages -await manager.deleteMessages(sessionId, ["msg-1"]); -``` - - - -### Forking - -Fork a session at a specific message — copies history up to that point into a new session: - - - -```ts -const forked = await manager.fork(sessionId, atMessageId, "Forked Chat"); -// forked.parent_session_id === sessionId -``` - - - -### Compaction helpers - - - -```ts -// Add a compaction overlay -await manager.addCompaction(sessionId, summary, fromId, toId); - -// Get overlays -const compactions = await manager.getCompactions(sessionId); - -// Compact and split — marks old session as ended, creates a continuation -const continuation = await manager.compactAndSplit( - sessionId, - summary, - "Continued Chat", -); -``` - - - -`compactAndSplit()` creates a new session with a summary message instead of an in-place overlay. The original session is marked with `end_reason: "compaction"`. - -### Usage tracking - - - -```ts -await manager.addUsage(sessionId, inputTokens, outputTokens, cost); ``` - - -### Cross-session search - - +These events are a local cache feed, not a durable log. -```ts -// Search across all sessions (FTS5) -const results = await manager.search("deployment Friday", { limit: 20 }); +## Storage -// Get tools for the model (includes session_search) -const tools = await manager.tools(); -``` - - +`Sessions` creates tables with the `cf_agents_session_` prefix. A text append costs one billed row write on an object that has never searched. -## Custom providers +## On the legacy Agent class -Implement any of the four provider interfaces to plug in your own storage: +Install `Sessions` on an Agent in the constructor: ```ts -// Read-only context -const myProvider: ContextProvider = { - get: async () => "Static content here", -}; - -// Writable context (enables set_context tool) -const myWritable: WritableContextProvider = { - get: async () => fetchFromMyDB(), - set: async (content) => saveToMyDB(content), -}; - -// Skill provider (enables load_context tool) -const mySkills: SkillProvider = { - get: async () => "- api-ref: API Reference\n- guide: User Guide", - load: async (key) => fetchDocument(key), - set: async (key, content, description) => - saveDocument(key, content, description), -}; - -// Search provider (enables search_context tool) -const mySearch: SearchProvider = { - get: async () => "42 entries indexed", - search: async (query) => searchMyIndex(query), - set: async (key, content) => indexContent(key, content), -}; -``` - - - -You can also implement `SessionProvider` to replace the SQLite storage entirely: - - - -```ts -const myStorage: SessionProvider = { - async getMessage(id) { - /* ... */ - }, - async getHistory(leafId?) { - /* ... */ - }, - async getLatestLeaf() { - /* ... */ - }, - async getBranches(messageId) { - /* ... */ - }, - async getPathLength(leafId?) { - /* ... */ - }, - async appendMessage(message, parentId?) { - /* ... */ - }, - async updateMessage(message) { - /* ... */ - }, - async deleteMessages(messageIds) { - /* ... */ - }, - async clearMessages() { - /* ... */ - }, - async addCompaction(summary, fromId, toId) { - /* ... */ - }, - async getCompactions() { - /* ... */ - }, - async searchMessages(query, limit) { - /* ... */ - }, -}; -``` - - - -## Postgres providers - -By default, Session storage uses Durable Object SQLite and creates tables lazily. If you need session data in an external Postgres database for cross-agent queries, analytics, or shared storage, use `PostgresSessionProvider`, `PostgresContextProvider`, and `PostgresSearchProvider`. - -These providers work with Postgres-compatible databases through [Hyperdrive](/hyperdrive/) for connection pooling. - -### 1. Create a Hyperdrive config - -Create a Hyperdrive config for your Postgres database: - -```sh -npx wrangler hyperdrive create my-session-db \ - --connection-string="postgresql://user:password@host:port/dbname" -``` - -Then add the Hyperdrive binding to `wrangler.jsonc`: - - - -```toml -compatibility_flags = ["nodejs_compat"] - -[[hyperdrive]] -binding = "HYPERDRIVE" -id = "" - -[placement] -mode = "smart" -``` - - - -If you know your database region, configure placement close to the database to reduce query latency. - -### 2. Create the tables - -The Postgres user may not have permission to create tables at runtime. Run the schema once in your database console: - -```sql -CREATE TABLE IF NOT EXISTS assistant_messages ( - id TEXT NOT NULL, - session_id TEXT NOT NULL DEFAULT '', - parent_id TEXT, - role TEXT NOT NULL, - content TEXT NOT NULL, - text_content TEXT NOT NULL DEFAULT '', - created_at TIMESTAMPTZ DEFAULT NOW(), - content_tsv TSVECTOR GENERATED ALWAYS AS (to_tsvector('english', text_content)) STORED, - PRIMARY KEY (session_id, id) -); - -CREATE INDEX IF NOT EXISTS idx_assistant_msg_parent - ON assistant_messages (parent_id); -CREATE INDEX IF NOT EXISTS idx_assistant_msg_session - ON assistant_messages (session_id); -CREATE INDEX IF NOT EXISTS idx_assistant_msg_fts - ON assistant_messages USING GIN (content_tsv); - -CREATE TABLE IF NOT EXISTS assistant_compactions ( - id TEXT PRIMARY KEY, - session_id TEXT NOT NULL DEFAULT '', - summary TEXT NOT NULL, - from_message_id TEXT NOT NULL, - to_message_id TEXT NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW() -); - -CREATE TABLE IF NOT EXISTS cf_agents_context_blocks ( - label TEXT PRIMARY KEY, - content TEXT NOT NULL, - updated_at TIMESTAMPTZ DEFAULT NOW() -); - -CREATE TABLE IF NOT EXISTS cf_agents_search_entries ( - label TEXT NOT NULL, - key TEXT NOT NULL, - content TEXT NOT NULL, - content_tsv TSVECTOR GENERATED ALWAYS AS (to_tsvector('english', content)) STORED, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - PRIMARY KEY (label, key) -); - -CREATE INDEX IF NOT EXISTS idx_search_entries_fts - ON cf_agents_search_entries USING GIN (content_tsv); -``` - -### 3. Wire it up - -Install `pg`, then create a client from the Hyperdrive connection string and pass it to the Postgres providers: - - +import { Agent } from "agents"; +import { Sessions } from "agents/sessions"; - +export class ChatAgent extends Agent { + readonly sessions = new Sessions(); + readonly session = this.sessions.session(); -```ts -import { Agent } from "agents"; -import { - PostgresContextProvider, - PostgresSearchProvider, - PostgresSessionProvider, - Session, -} from "agents/experimental/memory/session"; -import { Client } from "pg"; - -export class MyAgent extends Agent { - private session?: Session; - private pgClient?: Client; - - async onStart(): Promise { - const client = new Client({ - connectionString: this.env.HYPERDRIVE.connectionString, - }); - await client.connect(); - this.pgClient = client; - - const sessionId = this.ctx.id.toString(); - this.session = Session.create( - new PostgresSessionProvider(client, sessionId), - ) - .withContext("soul", { - provider: { - get: async () => "You are a helpful assistant.", - }, - }) - .withContext("memory", { - description: "Short facts", - maxTokens: 1100, - provider: new PostgresContextProvider(client, `memory_${sessionId}`), - }) - .withContext("knowledge", { - description: "Searchable knowledge base", - provider: new PostgresSearchProvider(client), - }) - .withCachedPrompt( - new PostgresContextProvider(client, `_prompt_${sessionId}`), - ); + constructor(ctx: DurableObjectState, env: Env) { + super(ctx, env); + this.lifecycle.use(this.sessions); } } ``` -### Behavior differences - -When `Session.create()` receives a `SessionProvider` instead of a SQLite-backed provider, it skips SQLite auto-wiring: - -- **Context blocks need explicit providers.** Each `withContext()` call that should persist data needs a `provider` option. -- **`withCachedPrompt()` needs an explicit provider.** Pass a `PostgresContextProvider` to persist the frozen system prompt. -- **Session methods are async.** Use `await` for reads and writes so the same code works with local SQLite and external storage. -- **Broadcaster support is skipped.** WebSocket status broadcasts for Session events only work with SQLite-backed sessions. - -### System prompt lifecycle - -`freezeSystemPrompt()` returns the cached prompt from storage. On first call, it loads context blocks from providers, renders the prompt, and persists it. Subsequent calls return the stored value without re-rendering. - -Use `refreshSystemPrompt()` to force reload context blocks, re-render the prompt, and update the stored value. - -## Storage tables - -By default, storage is in Durable Object SQLite and tables are created lazily on first use. Postgres-backed sessions use the external tables shown in the Postgres providers section. - -| Table | Purpose | -| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| `assistant_messages` | Tree-structured messages with `id`, `session_id`, `parent_id`, `role`, `content` (JSON), `created_at` | -| `assistant_compactions` | Compaction overlays with `summary`, `from_message_id`, `to_message_id` | -| `assistant_fts` | FTS5 virtual table for message search (porter stemming, unicode tokenization) | -| `assistant_sessions` | Session registry (SessionManager only) with `name`, `parent_session_id`, `model`, `source`, token/cost counters | -| `cf_agents_context_blocks` | Persistent context block storage (`AgentContextProvider`) | -| `cf_agents_search_entries` / `cf_agents_search_fts` | Searchable context entries and FTS5 index (`AgentSearchProvider`) | - -## Acknowledgments - -- Session's tree-structured messages are inspired by [Pi](https://pi.dev). -- Context blocks are inspired by [Letta AI memory blocks](https://www.letta.com/blog/memory-blocks). -- Formatting of blocks is inspired by [Hermes Agent](https://github.com/nousresearch/hermes-agent). - -## Related - -- [Think](/agents/harnesses/think/) — opinionated chat agent that uses Session for conversation storage via `configureSession()` -- [Chat agents](/agents/agent-class/communication-channels/chat/chat-agents/) — `AIChatAgent` with its own message persistence layer -- [Store and sync state](/agents/agent-class/state/) — `setState()` for simpler key-value persistence +The [sessions example](https://github.com/cloudflare/agents/tree/main/examples/next/sessions) serves a session over HTTP from a plain Durable Object. diff --git a/src/content/docs/agents/capabilities/state.mdx b/src/content/docs/agents/capabilities/state.mdx new file mode 100644 index 00000000000..c09e67e95ec --- /dev/null +++ b/src/content/docs/agents/capabilities/state.mdx @@ -0,0 +1,120 @@ +--- +title: State +description: Store one validated value in a Durable Object with the State capability, and sync it to useAgent clients in real time with WebSockets. +pcx_content_type: reference +sidebar: + order: 7 +products: + - agents +--- + +import { TypeScriptExample } from "~/components"; + +`State` from `agents/state` stores one JSON value in the object's SQLite database. It loads the value on first read, keeps it cached in memory, validates every change, and calls you back after each one. Combine it with [`WebSockets`](/agents/capabilities/websockets/) to sync the value with connected clients. + +## Install the capability + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { State } from "agents/state"; + +type Settings = { theme: "light" | "dark"; notifications: boolean }; + +export class SettingsAgent extends DurableObject { + readonly settings = new State({ + initialState: { theme: "light", notifications: true }, + }); + readonly lifecycle = Lifecycle.install(this).use(this.settings); + + async onRequest(request: Request): Promise { + if (request.method === "PUT") { + this.settings.set(await request.json()); + } + return Response.json(this.settings.get()); + } +} +``` + + + +| Method | Description | +| ------------------------- | ----------------------------------------------------------------------------------- | +| `get()` | Returns the current value, or `undefined` when nothing is stored | +| `set(next, source?)` | Validates and stores `next`, then calls `onChanged`. `source` defaults to `"server"` | + +`set()` writes synchronously. Install one `State` per value you store. + +## Options + +| Option | Description | +| --------------------- | ------------------------------------------------------------------------------------------------- | +| `initialState` | Stored on first read when no value exists | +| `validateStateChange` | `(next, source) => void`. Throw to reject a change before it is stored. It must be synchronous | +| `onChanged` | `(state, source) => void \| Promise`. Runs after a change is stored. A failure does not undo the write | + +`source` is `"server"` for changes your code makes, or the `Connection` that sent the change. + +## Sync state with clients + +Pass the same `State` instance to `WebSockets` and to the Lifecycle. Call `broadcastState()` from `onChanged` to tell other clients about each change: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { State } from "agents/state"; +import { WebSockets } from "agents/websockets"; + +type Counter = { count: number }; + +export class CounterAgent extends DurableObject { + readonly state: State = new State({ + initialState: { count: 0 }, + validateStateChange: (next) => { + if (!Number.isSafeInteger(next.count) || next.count < 0) { + throw new Error("count must be a non-negative integer"); + } + }, + onChanged: (_state, source) => this.webSockets.broadcastState(source), + }); + readonly webSockets: WebSockets = new WebSockets({ state: this.state }); + readonly lifecycle = Lifecycle.install(this) + .use(this.state) + .use(this.webSockets); +} +``` + + + +On connect, `WebSockets` sends each client the current value after its identity frame. When a client calls `setState()`, the change goes through `validateStateChange` before it is stored. A rejected change is logged and answered with a generic error frame. `broadcastState(source)` sends the new value to every connection except `source`. + +Read and update the value from React: + +```tsx +import { useAgent } from "agents/react"; + +function Counter() { + const agent = useAgent<{ count: number }>({ + agent: "counter-agent", + name: "demo", + }); + const count = agent.state?.count ?? 0; + return ; +} +``` + +Use the `readonly` option on `WebSockets` to refuse state updates from some connections. A readonly connection still receives state and can call methods. + +`connection.setState()` is a different feature. It stores a value on one connection and never syncs it to other clients. + +## Storage + +`State` stores its value in the `cf_agents_state` table. Store large or relational data in your own tables instead, because every change rewrites the whole value. + +## On the legacy Agent class + +The [`Agent` class](/agents/agent-class/) installs a `State` and a `WebSockets` wired together. Its `initialState`, `this.state`, `this.setState()`, and `onStateChanged()` call them. For more information, refer to [Store and sync state](/agents/agent-class/state/). diff --git a/src/content/docs/agents/capabilities/streams.mdx b/src/content/docs/agents/capabilities/streams.mdx new file mode 100644 index 00000000000..51246782a12 --- /dev/null +++ b/src/content/docs/agents/capabilities/streams.mdx @@ -0,0 +1,186 @@ +--- +title: Streams +description: Store incremental output in a Durable Object with the Streams capability, so clients can replay what they missed, follow new chunks, and resume over SSE. +pcx_content_type: reference +sidebar: + order: 9 + badge: Experimental +products: + - agents +--- + +import { TypeScriptExample } from "~/components"; + +:::caution[Experimental] +Everything exported from `agents/streams` may change between releases. +::: + +`Streams` from `agents/streams` stores incremental output, such as tokens from a model, as an ordered log of chunks. Each chunk gets the next number in the stream, called its cursor. A client that reconnects replays from its last cursor and then follows new chunks. A producer that dies leaves exactly the chunks it wrote, so a new producer can continue from the stream's cursor. + +`Streams` needs no alarm. + +## Install the capability + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { Streams } from "agents/streams"; + +export class ReplyAgent extends DurableObject { + readonly streams = new Streams(); + readonly lifecycle = Lifecycle.install(this).use(this.streams); +} +``` + + + +Pass `{ maxChunkBytes }` to change the largest chunk you can append. The default is 1 MiB. + +## Write a stream + +```ts +const stream = await this.streams.open("reply:123"); +stream.append({ text: "Hello" }); +stream.append({ text: " world" }); +stream.close(); +``` + +- `append(chunk)` writes one JSON chunk synchronously and returns its sequence number. It wakes every live reader. +- `close()` marks the stream completed. `error(reason)` marks it failed. +- `open()` is safe to call again. Opening a live stream returns a writer at its current `cursor`. Opening a settled stream throws `StreamClosedError`. + +Pass `{ tag }` to `open()` to label a stream with an application key, such as a request ID. Tags are not unique, so one operation can produce several streams, for example a retried reply. `list({ tag, limit: 1 })` returns the newest one. + +## Read a stream + +```ts +for await (const chunk of this.streams.read("reply:123", { from: 0 })) { + console.log(chunk); +} +``` + +`read()` replays the stored chunks from `from`, follows new chunks as they arrive, and ends when the stream settles. Reading does not depend on the producer being alive. + +When each delivery has a cost, such as one SSE flush or one RPC call, read in batches: + +```ts +for await (const batch of this.streams.readBatches("reply:123", { batchSize: 50 })) { + send(batch); +} +``` + +`readBatches()` also accepts `onUpToDate`, which runs once when the reader catches up with the stored chunks. + +| Method | Description | +| -------------------- | ------------------------------------------------------------------------------ | +| `status(id)` | Returns `{ state, cursor }` for a stream, or `null`. `state` is `streaming`, `completed`, or `errored` | +| `list(options?)` | Lists streams, newest first. Filter by `state` or `tag` | +| `delete(id)` | Deletes a settled stream and its chunks | + +## Serve a stream over SSE + +`sseResponse()` serves a stream as Server-Sent Events (SSE): + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { sseResponse, Streams } from "agents/streams"; + +export class ReplyAgent extends DurableObject { + readonly streams = new Streams(); + readonly lifecycle = Lifecycle.install(this).use(this.streams); + + onRequest(request: Request): Promise { + const id = new URL(request.url).searchParams.get("stream") ?? "reply:123"; + return sseResponse(this.streams, id, { request }); + } +} +``` + + + +Each event's SSE `id` is the chunk's sequence number. A reconnecting `EventSource` sends `Last-Event-ID`, and the response continues from the next chunk, with no client code. `?from=` works too. The response sends an `up-to-date` event when it reaches the stored tail, heartbeat comments while it waits, and a final `done` or `error` event. The request's abort signal stops the response when the client disconnects. + +## Produce a stream from a task + +A [`Tasks`](/agents/capabilities/tasks/) step can write to a stream. Start the producer loop at the stream's cursor, so a replay after an interruption continues instead of writing chunks twice: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { Streams } from "agents/streams"; +import { Tasks, type TaskStep } from "agents/tasks"; + +type GenerateInput = { streamId: string; total: number }; + +export class GenerateAgent extends DurableObject { + readonly streams = new Streams(); + readonly tasks = new Tasks({ + definitions: { + "generate@v1": async (input: GenerateInput, step: TaskStep) => { + await step.do("stream", async () => { + const stream = await this.streams.open(input.streamId); + for (let i = stream.cursor; i < input.total; i++) { + stream.append({ i, text: `chunk ${i}` }); + } + stream.close(); + }); + }, + }, + }); + readonly lifecycle = Lifecycle.install(this) + .use(this.streams) + .use(this.tasks); +} +``` + + + +## Turn a stream into a message + +A stream is temporary. Once its content is saved somewhere else, such as a session message, delete its rows. `close()` can run your own synchronous writes and delete the stream in one transaction: + +```ts +stream.close({ + commit: () => saveMessageSync(message), + discard: true, +}); +``` + +Either the commit runs and the stream is deleted, or the commit throws and the stream stays open. `commit` must be synchronous. `error(reason, { commit, discard })` works the same way for a failed producer. + +## Storage + +`Streams` stores chunks in blocks of up to 256 KB, so a long stream is a few rows. Each append is one billed row write. Streams are kept until you delete them or close them with `discard: true`. + +`AIChatAgent` and [Project Think](/agents/harnesses/think/) store each in-progress reply in `Streams`. + +## On the legacy Agent class + +Install `Streams` on an Agent in the constructor: + + + +```ts +import { Agent } from "agents"; +import { Streams } from "agents/streams"; + +export class ReplyAgent extends Agent { + readonly streams = new Streams(); + + constructor(ctx: DurableObjectState, env: Env) { + super(ctx, env); + this.lifecycle.use(this.streams); + } +} +``` + + + +The [streams example](https://github.com/cloudflare/agents/tree/main/examples/next/streams) serves a task-produced stream over SSE. diff --git a/src/content/docs/agents/capabilities/tasks.mdx b/src/content/docs/agents/capabilities/tasks.mdx new file mode 100644 index 00000000000..7e26f2c8809 --- /dev/null +++ b/src/content/docs/agents/capabilities/tasks.mdx @@ -0,0 +1,193 @@ +--- +title: Tasks +description: Run durable, replayable background work in a Durable Object with the Tasks capability, using journaled steps, durable sleeps, retries, and idempotency keys. +pcx_content_type: reference +sidebar: + order: 10 + badge: Experimental +products: + - agents +--- + +import { TypeScriptExample } from "~/components"; + +:::caution[Experimental] +Everything exported from `agents/tasks` may change between releases. +::: + +`Tasks` from `agents/tasks` runs background work as a sequence of named steps. A run survives eviction, deploys, and crashes. When a run resumes, completed steps return their stored results, sleeps check their stored deadlines, and work continues from the first unfinished step. + +Each run's next wake time is a job in the [Lifecycle job queue](/agents/lifecycle/job-queue/), so `Tasks` shares the object's alarm with `Scheduler` and every other capability. + +## Install the capability + +Declare task definitions in the constructor, then install the capability on the [Lifecycle](/agents/lifecycle/): + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +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 research = await step.do( + "research", + { retries: { limit: 4, delay: "2 seconds", backoff: "exponential" } }, + ({ signal }) => this.research(input.topic, signal), + ); + + await step.sleep("editorial-delay", "30 seconds"); + + await step.status("Publishing"); + await step.do("publish", ({ idempotencyKey }) => + this.publish(input.reportId, research, idempotencyKey), + ); + return { reportId: input.reportId }; + }, + }, + }); + readonly lifecycle = Lifecycle.install(this).use(this.tasks); + + async research(topic: string, signal: AbortSignal): Promise { + const response = await fetch(`https://example.com/search?q=${topic}`, { signal }); + return response.text(); + } + + async publish(id: string, body: string, idempotencyKey: string): Promise { + await this.env.REPORTS.put(`reports/${id}.txt`, body, { + customMetadata: { idempotencyKey }, + }); + } +} +``` + + + +The definitions map is the registry. Storage keeps only the definition name, and the map is rebuilt every time the object starts, so a run that was in progress before an eviction finds its handler afterwards. Put a version in the name, such as `"build-report@v2"`, instead of changing the steps of a definition with runs in progress. + +| Option | Description | +| ------------- | ------------------------------------------------------------------------------- | +| `definitions` | Named task handlers `(input, step) => result` | +| `retries` | Default step retry policy. Defaults to five attempts with exponential backoff | +| `stepTimeout` | Default timeout for one step attempt. Defaults to five minutes | +| `onError` | Observes a run that fails | + +## Start a run + +```ts +const receipt = await this.tasks.run( + "build-report@v1", + { reportId: "42", topic: "durable execution" }, + { idempotencyKey: "report:42" }, +); +``` + +`run()` stores the run and returns a receipt without waiting for it to finish. A second `run()` with the same `idempotencyKey` joins the existing run instead of starting another, and its receipt has `accepted: false`. + +`run()` also accepts `runId` to choose the ID yourself, `metadata` to store JSON with the run, and `retain: false` to delete the run's record once it settles. + +Inputs, step results, metadata, and final results must be JSON-serializable and at most 1 MiB each. + +`tasks.handle(name)` returns a typed handle for one definition, with its own `run()`, `get()`, `getByIdempotencyKey()`, and `cancel()`: + +```ts +const buildReport = this.tasks.handle("build-report@v1"); +const run = await buildReport.get(receipt.runId); +``` + +## Steps + +| Method | Behavior | +| ----------------------------- | ------------------------------------------------------------------------------------------- | +| `step.do(name, config?, fn)` | Runs `fn` once and stores its result. A replay returns the stored result without running `fn` | +| `step.sleep(name, duration)` | Stores a wake time and suspends the run. Nothing stays in memory while it waits | +| `step.sleepUntil(name, when)` | Sleeps until a date or Unix timestamp in milliseconds | +| `step.status(message)` | Updates the run's progress message | +| `step.idempotencyKey(name)` | Returns the stable 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. `config` sets `retries` and `timeout` for one step. + +A step that throws retries after a durable delay. Throw `NonRetryableError` from `agents/tasks` to fail the run immediately. + +## Write replay-safe handlers + +Every attempt runs the handler again from its first line. To keep that safe: + +- Put every external side effect inside a `step.do()`. +- Keep code between steps fast and deterministic. 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. An interrupted step runs again, so delivery is at least once. + +`step.interrupted` is `{ name, attempt }` when the previous attempt's isolate died 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 replay finds stored steps its code could not have written, the run 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 + +```ts +const snapshot = await this.tasks.get(receipt.runId); +const recent = await this.tasks.list({ definition: "build-report@v1" }); +await this.tasks.cancel(receipt.runId, "superseded"); +await this.tasks.delete({ settledBefore: new Date(Date.now() - 86_400_000) }); +``` + +A run's `state` is one of these values: + +| State | Meaning | +| ----------- | ---------------------------------------------------------- | +| `pending` | Accepted, and not started yet | +| `running` | An attempt is running | +| `waiting` | Suspended until a wake time, for a sleep or a retry | +| `completed` | Finished, with a `result` | +| `failed` | Finished, with an `error` | +| `cancelled` | Cancelled, with an optional `reason` | + +Cancellation is cooperative. A waiting run settles immediately. A running attempt is aborted through its signal and settles at its next step. An external effect that already happened cannot be undone. + +## Choose an API + +| You need | Use | +| ------------------------------------------------------------- | --------------------------------------------------------- | +| Short async work during a request | An ordinary `await` | +| A callback at a time, on a cron, or on an interval | [`Scheduler`](/agents/capabilities/scheduler/) | +| Background callbacks in push order | [`Queue`](/agents/capabilities/queue/) | +| Multi-step work on one object, with retries and sleeps | `Tasks` | +| Orchestration across services, with a dashboard | [Cloudflare Workflows](/workflows/) | + +## On the legacy Agent class + +The [`Agent` class](/agents/agent-class/) installs `Tasks` as `this.tasks`. Declare definitions on its `taskDefinitions` field: + + + +```ts +import { Agent } from "agents"; +import type { TaskHandlers, TaskStep } from "agents/tasks"; + +export class ReportAgent extends Agent { + override readonly taskDefinitions = { + "build-report@v1": async (input: { topic: string }, step: TaskStep) => { + return step.do("draft", () => `Draft about ${input.topic}`); + }, + } satisfies TaskHandlers; +} +``` + + + +`AIChatAgent` and [Project Think](/agents/harnesses/think/) run their chat turns on this capability. The Agent class also keeps its older [fiber](/agents/agent-class/durable-execution/) APIs, `runFiber()` and `startFiber()`. + +The [tasks example](https://github.com/cloudflare/agents/tree/main/examples/next/tasks) runs a report task with a durable sleep on a plain Durable Object. diff --git a/src/content/docs/agents/capabilities/websockets.mdx b/src/content/docs/agents/capabilities/websockets.mdx new file mode 100644 index 00000000000..135b909813d --- /dev/null +++ b/src/content/docs/agents/capabilities/websockets.mdx @@ -0,0 +1,161 @@ +--- +title: WebSockets +description: Accept hibernating WebSocket connections on a Durable Object, expose RPC methods to useAgent clients, and tag and broadcast to connections with the WebSockets capability. +pcx_content_type: reference +sidebar: + order: 6 +products: + - agents +--- + +import { TypeScriptExample } from "~/components"; + +The [Lifecycle](/agents/lifecycle/) does not accept WebSocket connections on its own. `WebSockets` from `agents/websockets` is the capability that does. It accepts upgrades, hibernates idle connections, and speaks the Agents protocol, so a plain Durable Object works with `useAgent` and `AgentClient` from the [client SDK](/agents/agent-class/communication-channels/chat/client-sdk/). + +## Install the capability + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { WebSockets } from "agents/websockets"; + +export class RoomAgent extends DurableObject { + readonly webSockets: WebSockets = new WebSockets({ + handlers: { + onConnect: (connection) => { + connection.send(JSON.stringify({ type: "welcome", id: connection.id })); + }, + onMessage: (connection, message) => { + for (const other of this.webSockets.getConnections()) { + if (other.id !== connection.id) other.send(message); + } + }, + }, + }); + readonly lifecycle = Lifecycle.install(this).use(this.webSockets); +} +``` + + + +`WebSockets` claims every upgrade, even without handlers. It is a catch-all for upgrades, so the Lifecycle offers each upgrade to other capabilities first. Without the capability, the Lifecycle declines upgrades. + +## Handle connections + +The handlers run inside the host's invocation context, with the connection available from [`getCurrentAgent()`](/agents/lifecycle/get-current-agent/): + +| Handler | When it runs | +| -------------------------------------------- | ---------------------------------------------------------------- | +| `onConnect(connection, ctx)` | After the upgrade is accepted. `ctx.request` is the upgrade request | +| `onMessage(connection, message)` | For each message that is not an Agents protocol frame | +| `onClose(connection, code, reason, wasClean)` | When the connection closes | +| `onError(connection, error)` | When the connection fails mid-session | + +A `Connection` has an `id`, `send()`, and `close()`. `connection.setState()` stores a small value on the connection itself. That value survives hibernation and is read back with `connection.state`. + +Use these methods on the capability to reach other connections: + +| Method | Description | +| ----------------------- | ---------------------------------------------------- | +| `getConnections(tag?)` | Every open connection, or those with one tag | +| `getConnection(id)` | One connection by ID, or `undefined` | + +### Tag connections + +Pass `getConnectionTags` to label each connection when it is accepted. The connection ID is always the first tag: + +```ts +readonly webSockets = new WebSockets({ + getConnectionTags: (_connection, ctx) => { + const room = new URL(ctx.request.url).searchParams.get("room"); + return room ? [`room:${room}`] : []; + }, +}); +``` + +Then send to one group with `this.webSockets.getConnections("room:general")`. + +## Expose methods to clients + +Pass an `RpcTarget` as `callables`. Its methods are what clients call with `agent.stub.method()` or `agent.call("method")`: + + + +```ts +import { DurableObject, RpcTarget } from "cloudflare:workers"; +import { Lifecycle } from "agents/lifecycle"; +import { WebSockets } from "agents/websockets"; + +class CounterMethods extends RpcTarget { + readonly storage: DurableObjectStorage; + + constructor(storage: DurableObjectStorage) { + super(); + this.storage = storage; + } + + async increment(): Promise { + const count = ((await this.storage.get("count")) ?? 0) + 1; + await this.storage.put("count", count); + return count; + } +} + +export class CounterAgent extends DurableObject { + readonly webSockets = new WebSockets({ + callables: new CounterMethods(this.ctx.storage), + }); + readonly lifecycle = Lifecycle.install(this).use(this.webSockets); +} +``` + + + +The methods run inside the host's invocation context, with the calling connection in scope. Treat their arguments as untrusted input, because any connected client can call them. + +Connect from React with `useAgent`: + +```tsx +import { useAgent } from "agents/react"; + +function Counter() { + const agent = useAgent({ agent: "counter-agent", name: "demo" }); + return ; +} +``` + +Route the binding from your Worker with [`routeAgentRequest()`](/agents/lifecycle/#route-requests-to-your-agent), and authenticate the request before you route it. + +## Choose a transport + +The client picks the transport with its `transport` option: + +| Transport | Behavior | +| --------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| `"cf-websocket"` (default) | Hibernating connections. Callables travel as JSON `rpc` frames. Idle clients stay connected while the object leaves memory | +| `"capnweb"` (experimental) | Native [Cap'n Web](https://github.com/cloudflare/capnweb) calls. A returned `RpcTarget` is a live stub, streams work, and calls pipeline. An open connection keeps the object in memory | + +Both transports call the same handlers and show up in `getConnections()`. + +With the default transport, a message wakes a hibernated object. The constructor and Lifecycle startup run again before `onMessage`. Keep anything you need after a wake in storage or in `connection.setState()`. + +## Sync state + +Pass a [`State`](/agents/capabilities/state/) capability as `state` to sync one value with `useAgent().state`. For more information, refer to [Sync state with clients](/agents/capabilities/state/#sync-state-with-clients). + +## Control the protocol per connection + +By default, every connection gets the Agents protocol frames: an identity frame on connect, then the current state when `state` is set. + +| Option | Description | +| ---------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `protocol` | `true` (default), `false`, or a function that decides per connection. A connection without the protocol still sends and receives messages and calls methods | +| `readonly` | A function that decides per connection whether its state updates are refused. Change it later with `setReadonly(connection)` | + +Set `protocol: false` to send the connect frames yourself with `sendConnectFrames()`, or with `sendIdentity()` and `sendState()`. + +## On the legacy Agent class + +The [`Agent` class](/agents/agent-class/) installs `WebSockets` and calls its own `onConnect()`, `onMessage()`, `onClose()`, and `onError()` methods. Its `@callable()` methods are its client interface. For more information, refer to [WebSockets](/agents/agent-class/websockets/). diff --git a/src/content/docs/agents/concepts/conversation-state-and-memory.mdx b/src/content/docs/agents/concepts/conversation-state-and-memory.mdx index 1925dd23835..721ea09ce18 100644 --- a/src/content/docs/agents/concepts/conversation-state-and-memory.mdx +++ b/src/content/docs/agents/concepts/conversation-state-and-memory.mdx @@ -16,7 +16,7 @@ Agents need memory to be useful over time. Without it, every conversation starts An agent on Cloudflare has two kinds of memory, and both live in its Durable Object's SQLite database: -- **Conversation history** is the messages and tool calls that make up a conversation. [Harnesses](/agents/harnesses/) store it for you. To store it yourself, use the [`Sessions`](/agents/capabilities/#sessions) capability from `agents/sessions`. +- **Conversation history** is the messages and tool calls that make up a conversation. [Harnesses](/agents/harnesses/) store it for you. To store it yourself, use the [`Sessions`](/agents/capabilities/sessions/) capability from `agents/sessions`. - **Context** is what the model reads in its system prompt on every turn: its identity, facts it learned, and knowledge it can search. Assemble it with `ContextBlocks` from `agents/context`. The two are separate on purpose, so an agent can have a prompt without a transcript, or a transcript without a prompt. @@ -25,7 +25,7 @@ The two are separate on purpose, so an agent can have a prompt without a transcr `agents/sessions` and `agents/context` are experimental. Their APIs may change between releases. ::: -For small UI state that syncs to clients, use the [`State`](/agents/capabilities/#state) capability instead. +For small UI state that syncs to clients, use the [`State`](/agents/capabilities/state/) capability instead. ## Conversation history @@ -127,7 +127,7 @@ Refresh the frozen system prompt after a compaction, so the prompt and the summa diff --git a/src/content/docs/agents/getting-started/add-to-existing-project.mdx b/src/content/docs/agents/getting-started/add-to-existing-project.mdx index 553b4987bed..a5c70e6e3dd 100644 --- a/src/content/docs/agents/getting-started/add-to-existing-project.mdx +++ b/src/content/docs/agents/getting-started/add-to-existing-project.mdx @@ -173,7 +173,7 @@ export class Counter extends DurableObject { -For recurring or cron work, install the [`Scheduler`](/agents/capabilities/#scheduler) capability instead. For more information, refer to [Jobs and alarms](/agents/lifecycle/job-queue/). +For recurring or cron work, install the [`Scheduler`](/agents/capabilities/scheduler/) capability instead. For more information, refer to [Jobs and alarms](/agents/lifecycle/job-queue/). ### Move WebSocket handlers to the `WebSockets` capability diff --git a/src/content/docs/agents/harnesses/index.mdx b/src/content/docs/agents/harnesses/index.mdx index ee15ff57fb4..1769ef60c1c 100644 --- a/src/content/docs/agents/harnesses/index.mdx +++ b/src/content/docs/agents/harnesses/index.mdx @@ -18,7 +18,7 @@ Running an agent on Cloudflare takes three steps: 1. **Pick a harness.** Install a [harness](#choose-a-harness), such as the [Pi harness](/agents/harnesses/pi/), or [build your own](#build-your-own-harness). 2. **Run it on a Durable Object.** Install the harness with the [Lifecycle](/agents/lifecycle/). The Lifecycle runs startup, routes requests, and owns the object's job queue and alarm, so the harness wakes and carries on after eviction, deploys, and crashes. -3. **Plug in capabilities.** Add an [MCP client](/agents/capabilities/#mcp-client), a [scheduler](/agents/capabilities/#scheduler), [WebSockets](/agents/capabilities/#websockets), or any other [capability](/agents/capabilities/) the agent needs. Each one is a field and one more `.use()` call. +3. **Plug in capabilities.** Add an [MCP client](/agents/capabilities/mcp-client/), a [scheduler](/agents/capabilities/scheduler/), [WebSockets](/agents/capabilities/websockets/), or any other [capability](/agents/capabilities/) the agent needs. Each one is a field and one more `.use()` call. diff --git a/src/content/docs/agents/harnesses/pi/index.mdx b/src/content/docs/agents/harnesses/pi/index.mdx index b70452f0541..b2bd3e3df3b 100644 --- a/src/content/docs/agents/harnesses/pi/index.mdx +++ b/src/content/docs/agents/harnesses/pi/index.mdx @@ -35,7 +35,7 @@ To start from a complete app, deploy the [Pi harness example](https://github.com ## How it works -`PiHarness` is a new "Lifecycle capability" provided by the Cloudflare Agents SDK. Pi Durable provides the agent harness and the [Lifecycle](https://github.com/cloudflare/agents/blob/main/docs/agents/lifecycle.md) is responsible for keeping the agent running in the Durable Object. The Lifecycle is a core concept in the Agents SDK ensuring that long-running work can run in a Durable Object, surviving restarts, crashes, and network issues. A Lifecycle capability is a reusable piece of a Durable Object that the Lifecycle starts and gives SQLite storage and a durable job queue. `PiHarness` uses them in two ways: +`PiHarness` is a new "Lifecycle capability" provided by the Cloudflare Agents SDK. Pi Durable provides the agent harness and the [Lifecycle](/agents/lifecycle/) is responsible for keeping the agent running in the Durable Object. The Lifecycle is a core concept in the Agents SDK ensuring that long-running work can run in a Durable Object, surviving restarts, crashes, and network issues. A Lifecycle capability is a reusable piece of a Durable Object that the Lifecycle starts and gives SQLite storage and a durable job queue. `PiHarness` uses them in two ways: - **Storage.** Pi keeps its transcripts, inbox, and tasks in its own tables in the object's SQLite database. The table names start with `pi_`, so they do not collide with your own tables. - **Wake-up.** Pi's scheduler runs in memory, and an evicted object has none. `PiHarness` schedules one Lifecycle job for the object, driven by Pi's `wake()`. The job keeps a heartbeat while Pi works, is due when Pi's next sleeping task wakes, and completes when Pi has nothing to do. If the object is evicted mid-run, the job's alarm restarts it, Pi reopens its storage, and the run continues. @@ -122,6 +122,8 @@ export default { +Address the object by name, for example with `env.Assistant.getByName("demo")`. You can install other [capabilities](/agents/capabilities/), such as `Scheduler` or `WebSockets`, on the same Lifecycle. + The `harness` factory receives `storage`, Pi's storage over the object's SQLite database, `context`, a background context for opening Pi, and `onWake`, so the object can hibernate while Pi's tasks sleep. Everything else that `Harness.open()` takes is yours to build in the factory: `models`, the `registry` of tools and prompt sections, `settings`, `env`, and `onReport`. The factory runs inside the object's startup, before any session is used, and again whenever the harness reopens Pi after a storage error (refer to [Recovery](#recovery)). Keep it safe to run more than once. If it throws, the operation that started it fails, and the next operation tries again. Every `PiHarness` operation waits for startup, so a call that arrives over RPC before startup does not open Pi on its own. diff --git a/src/content/docs/agents/harnesses/think/recovery.mdx b/src/content/docs/agents/harnesses/think/recovery.mdx index 366f73c0f7f..f07f80cc22a 100644 --- a/src/content/docs/agents/harnesses/think/recovery.mdx +++ b/src/content/docs/agents/harnesses/think/recovery.mdx @@ -194,7 +194,7 @@ This runs during transcript repair — before the repaired transcript is persist ## Context-window overflow recovery -[Compaction](/agents/capabilities/sessions/#compaction) is checked **between turns** — `compactAfter()` runs after each `appendMessage()`. But a single long, tool-heavy turn grows the prompt step by step inside one `streamText` loop and can exceed the model context window **mid-turn**, before the next pre-turn check. The provider then rejects the request (`"prompt is too long"`, `context_length_exceeded`), and the turn would otherwise die terminally. +[Compaction](/agents/capabilities/sessions/#compact-history) is checked **between turns** — `compactAfter()` runs after each `appendMessage()`. But a single long, tool-heavy turn grows the prompt step by step inside one `streamText` loop and can exceed the model context window **mid-turn**, before the next pre-turn check. The provider then rejects the request (`"prompt is too long"`, `context_length_exceeded`), and the turn would otherwise die terminally. Think recovers from this with two opt-in, provider-agnostic layers, both configured through the `contextOverflow` property. Both are off by default, so existing behavior is unchanged. Both reuse your session's compaction function, so they require a `configureSession()` with `onCompaction()` configured. Both require [`classifyChatError`](/agents/harnesses/think/lifecycle-hooks/#classifychaterror) to tell Think which errors are overflows — Think ships no provider-specific matching in core. @@ -239,7 +239,7 @@ export class MyAgent extends Think { Use either layer alone, or both together: the proactive guard avoids most overflows, and the reactive backstop catches any that still slip through (for example, a turn that starts already over budget, or a single tool result so large that compaction cannot help — in which case it terminalizes cleanly). Both apply to every turn entry path (WebSocket, sub-agent `chat()`, and programmatic `saveMessages()` / `submitMessages()`), and both emit a `chat:context:compacted` [diagnostics channel event](/agents/platform/observability/diagnostics-channels/#chat-context-events). :::note -A no-op compaction cannot rescue an over-budget turn, so recovery is only as effective as your compaction configuration. For tool-heavy histories, configure a `tokenCounter` on `compactAfter()` (refer to [Sessions](/agents/capabilities/sessions/#auto-compaction)). +A no-op compaction cannot rescue an over-budget turn, so recovery is only as effective as your compaction configuration. For tool-heavy histories, configure a `tokenCounter` on `compactAfter()` (refer to [Sessions](/agents/capabilities/sessions/#compact-history)). ::: For a runnable demo against a real Workers AI model, refer to the [`context-overflow-recovery` example](https://github.com/cloudflare/agents/tree/main/examples/context-overflow-recovery). diff --git a/src/content/docs/agents/harnesses/think/tools.mdx b/src/content/docs/agents/harnesses/think/tools.mdx index 1a7b26b04ea..2c4cf3c6f48 100644 --- a/src/content/docs/agents/harnesses/think/tools.mdx +++ b/src/content/docs/agents/harnesses/think/tools.mdx @@ -458,7 +458,7 @@ binding = "LOADER" -This adds the durable CDP tool plus stateless [Quick Action](/agents/tools/browser/#quick-actions) tools when a `browser` binding is present: +This adds the durable CDP tool plus stateless [Quick Action](/agents/tools/browser/#quick-action-tools) tools when a `browser` binding is present: | Tool | Description | | ------------------ | --------------------------------------------------------------------------------------- | diff --git a/src/content/docs/agents/index.mdx b/src/content/docs/agents/index.mdx index 3928a45e550..3e11c6b3be8 100644 --- a/src/content/docs/agents/index.mdx +++ b/src/content/docs/agents/index.mdx @@ -67,7 +67,7 @@ An agent on Cloudflare has these parts: - **A harness** is the agent. It runs the loop around the model: prompts, tool calls, retries, and recovery. Start with the [Pi harness](/agents/harnesses/pi/), or one of the experimental harnesses: [OpenCode](/agents/harnesses/opencode/) or the [Container harness](/agents/harnesses/container/), which runs Claude Code or Codex. You can also extend [Project Think](/agents/harnesses/think/), or [build your own](/agents/harnesses/#build-your-own-harness). - **A Durable Object** is where the agent lives. Each instance has its own storage and address, so each user or task gets its own agent. - **The Lifecycle** keeps the harness running on the Durable Object. It runs startup, routes requests, and owns the object's durable job queue and its single alarm, so work continues across eviction, deploys, and crashes. -- **Capabilities** plug in what else the agent needs: an [MCP client](/agents/capabilities/#mcp-client), [schedules](/agents/capabilities/#scheduler), [queues](/agents/capabilities/#queue), [WebSockets](/agents/capabilities/#websockets) and [state](/agents/capabilities/#state) for clients, and [routed agents](/agents/capabilities/#routed-agents). +- **Capabilities** plug in what else the agent needs: an [MCP client](/agents/capabilities/mcp-client/), [schedules](/agents/capabilities/scheduler/), [queues](/agents/capabilities/queue/), [WebSockets](/agents/capabilities/websockets/) and [state](/agents/capabilities/state/) for clients, and [routed agents](/agents/capabilities/routed-agents/). - **Tools** let the model act: [browser automation](/agents/tools/browser/), [sandboxed code execution](/agents/tools/sandbox/), [AI Search](/agents/tools/ai-search/), [MCP tools](/agents/tools/mcp/), and [payments](/agents/tools/payments/). [Code Mode](/agents/tools/codemode/) lets models orchestrate tools by writing code. Before harnesses, you built an agent by extending the [`Agent` class](/agents/agent-class/). The `Agent` class is still supported. It is a Durable Object with the Lifecycle and a fixed set of capabilities already installed, and it is the base for `@callable()` methods, [chat](/agents/agent-class/communication-channels/chat/), [voice](/agents/agent-class/communication-channels/voice/), [email](/agents/agent-class/communication-channels/email/), and other [communication channels](/agents/agent-class/communication-channels/). For a new agent, start with a harness. diff --git a/src/content/docs/agents/lifecycle/job-queue.mdx b/src/content/docs/agents/lifecycle/job-queue.mdx index f746013a288..841592501ea 100644 --- a/src/content/docs/agents/lifecycle/job-queue.mdx +++ b/src/content/docs/agents/lifecycle/job-queue.mdx @@ -106,7 +106,7 @@ When the alarm fires, Lifecycle: Every change to the queue resets the alarm, so you never set it yourself. Do not call `ctx.storage.setAlarm()` on an object that uses Lifecycle, because that alarm would replace the one Lifecycle manages. -Keep each `onJob()` call short. Lifecycle awaits jobs one at a time, so a slow job delays every other job on the object. For long work, start it, record its progress in storage, and return. [`Tasks`](/agents/capabilities/#tasks) does this for you with replayable steps. +Keep each `onJob()` call short. Lifecycle awaits jobs one at a time, so a slow job delays every other job on the object. For long work, start it, record its progress in storage, and return. [`Tasks`](/agents/capabilities/tasks/) does this for you with replayable steps. ## Memory-limit protection From a1ef2aa484c8505bc92f71796e90f98c0d10a6fc Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 27 Oct 2026 11:34:17 +0000 Subject: [PATCH 08/11] [Agents] Drop the RPC-only env option from the MCP client capability --- src/content/docs/agents/capabilities/mcp-client.mdx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/content/docs/agents/capabilities/mcp-client.mdx b/src/content/docs/agents/capabilities/mcp-client.mdx index 8ec97b825a8..2967c787d7c 100644 --- a/src/content/docs/agents/capabilities/mcp-client.mdx +++ b/src/content/docs/agents/capabilities/mcp-client.mdx @@ -28,9 +28,7 @@ import { Lifecycle } from "agents/lifecycle"; import { MCPClientManager } from "agents/mcp/client"; export class ToolAgent extends DurableObject { - readonly mcp = new MCPClientManager("tool-agent", "1.0.0", { - env: this.env, - }); + readonly mcp = new MCPClientManager("tool-agent", "1.0.0"); readonly lifecycle = Lifecycle.install(this).use(this.mcp); } ``` @@ -39,7 +37,6 @@ export class ToolAgent extends DurableObject { | Option | Description | | -------------------- | --------------------------------------------------------------------------------------------------------- | -| `env` | The Worker's bindings. Required to reconnect to MCP servers reached over Durable Object RPC after a wake. | | `createAuthProvider` | Builds the OAuth provider for a stored HTTP server. Use it to replace the default provider. | ## Add a server @@ -54,9 +51,7 @@ import { Lifecycle } from "agents/lifecycle"; import { MCPClientManager } from "agents/mcp/client"; export class ToolAgent extends DurableObject { - readonly mcp = new MCPClientManager("tool-agent", "1.0.0", { - env: this.env, - }); + readonly mcp = new MCPClientManager("tool-agent", "1.0.0"); readonly lifecycle = Lifecycle.install(this).use(this.mcp); async onRequest(request: Request): Promise { From 665140061aae422eecad545f86992282c632e3ff Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 27 Oct 2026 11:10:55 +0000 Subject: [PATCH 09/11] [Agents] Changelog: run a harness on a Durable Object, with a stable Lifecycle --- .../2026-10-13-agents-harnesses-lifecycle.mdx | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/content/changelog/agents/2026-10-13-agents-harnesses-lifecycle.mdx diff --git a/src/content/changelog/agents/2026-10-13-agents-harnesses-lifecycle.mdx b/src/content/changelog/agents/2026-10-13-agents-harnesses-lifecycle.mdx new file mode 100644 index 00000000000..f97e396096c --- /dev/null +++ b/src/content/changelog/agents/2026-10-13-agents-harnesses-lifecycle.mdx @@ -0,0 +1,73 @@ +--- +title: Run an agent harness on a Durable Object, with a stable Lifecycle +description: The Agents SDK Lifecycle, Scheduler, State, WebSockets, and MCP client are now stable. Run a harness such as Pi, the AI SDK, or a coding agent in a Container on any Durable Object, and plug in capabilities. +products: + - agents + - durable-objects +date: 2026-10-13 +--- + +import { TypeScriptExample } from "~/components"; + +To run an agent on Cloudflare, pick a [harness](/agents/harnesses/), the loop around the model, and run it on a plain [Durable Object](/durable-objects/) with the [Lifecycle](/agents/lifecycle/). Then plug in the [capabilities](/agents/capabilities/) it needs: + + + +```ts +import { DurableObject } from "cloudflare:workers"; +import { PiHarness } from "agents/harness/pi"; +import { Lifecycle } from "agents/lifecycle"; +import { MCPClientManager } from "agents/mcp/client"; +import { Scheduler } from "agents/schedules"; + +export class Assistant extends DurableObject { + readonly harness = new PiHarness({ + /* model and tools */ + }); + readonly mcp = new MCPClientManager("assistant", "1.0.0", { env: this.env }); + readonly scheduler = new Scheduler({ callbacks: {} }); + + readonly lifecycle = Lifecycle.install(this) + .use(this.harness) + .use(this.mcp) + .use(this.scheduler); +} +``` + + + +In `agents@0.28.0`, `Lifecycle` is stable, along with the `Scheduler`, `State`, `WebSockets`, and `MCPClientManager` capabilities. The Lifecycle runs startup, routes requests, and owns the object's durable job queue and its single alarm, so the harness and its capabilities never overwrite each other's alarms, and their work resumes after eviction, deploys, and crashes. + +You can pick from these harnesses: + +| Harness | Import | Status | +| --------------------------------------------------------------------------------------------------- | -------------------------- | ------------ | +| [Pi](/agents/harnesses/pi/) | `agents/harness/pi` | Beta | +| [AI SDK harness](/agents/harnesses/ai-sdk/) | `agents/harness/ai-sdk` | Experimental | +| [Container harness](/agents/harnesses/container/), which runs Claude Code or Codex | `agents/harness/container` | Experimental | +| [OpenCode](/agents/harnesses/opencode/) | `agents/harness/opencode` | Experimental | + +To build your own, combine [Sessions](/agents/capabilities/sessions/), [Streams](/agents/capabilities/streams/), and [Tasks](/agents/capabilities/tasks/), or keep an outside agent's sessions in `HarnessStore` from `agents/harness/store`. + +These capabilities ship in the `agents` package: + +| Capability | Import | Status | +| ------------------------------------------------------ | ------------------- | ------------ | +| [`MCPClientManager`](/agents/capabilities/mcp-client/) | `agents/mcp/client` | Stable | +| [`Scheduler`](/agents/capabilities/scheduler/) | `agents/schedules` | Stable | +| [`WebSockets`](/agents/capabilities/websockets/) | `agents/websockets` | Stable | +| [`State`](/agents/capabilities/state/) | `agents/state` | Stable | +| [`Queue`](/agents/capabilities/queue/) | `agents/queue` | Experimental | +| [`RoutedAgents`](/agents/capabilities/routed-agents/) | `agents/routing` | Experimental | +| [`Browser`](/agents/capabilities/browser/) | `agents/browser` | Experimental | +| [`Sessions`](/agents/capabilities/sessions/) | `agents/sessions` | Experimental | +| [`Streams`](/agents/capabilities/streams/) | `agents/streams` | Experimental | +| [`Tasks`](/agents/capabilities/tasks/) | `agents/tasks` | Experimental | + +The `Agent` class is still supported, and is now documented as the legacy option under [Agent class](/agents/agent-class/). It is a Durable Object with the Lifecycle and a fixed set of capabilities already installed. Use it for the features that still need it, such as `@callable()` methods, chat agents, voice, and email. [Project Think](/agents/harnesses/think/) still extends `Agent`. + +To run your first agent this way, refer to the [quick start](/agents/getting-started/quick-start/). To move an existing Durable Object onto the Lifecycle, refer to [Add to an existing project](/agents/getting-started/add-to-existing-project/). Upgrade with: + +```sh +npm install agents@latest +``` From d81567d90bb3dc23e9363854fbcad38badc683b3 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 27 Oct 2026 11:35:30 +0000 Subject: [PATCH 10/11] [Agents] Date the changelog for today, show a complete Pi harness, and link the Codex example --- ...2026-10-27-agents-harnesses-lifecycle.mdx} | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) rename src/content/changelog/agents/{2026-10-13-agents-harnesses-lifecycle.mdx => 2026-10-27-agents-harnesses-lifecycle.mdx} (80%) diff --git a/src/content/changelog/agents/2026-10-13-agents-harnesses-lifecycle.mdx b/src/content/changelog/agents/2026-10-27-agents-harnesses-lifecycle.mdx similarity index 80% rename from src/content/changelog/agents/2026-10-13-agents-harnesses-lifecycle.mdx rename to src/content/changelog/agents/2026-10-27-agents-harnesses-lifecycle.mdx index f97e396096c..787fd03888e 100644 --- a/src/content/changelog/agents/2026-10-13-agents-harnesses-lifecycle.mdx +++ b/src/content/changelog/agents/2026-10-27-agents-harnesses-lifecycle.mdx @@ -4,7 +4,7 @@ description: The Agents SDK Lifecycle, Scheduler, State, WebSockets, and MCP cli products: - agents - durable-objects -date: 2026-10-13 +date: 2026-10-27 --- import { TypeScriptExample } from "~/components"; @@ -15,16 +15,31 @@ To run an agent on Cloudflare, pick a [harness](/agents/harnesses/), the loop ar ```ts import { DurableObject } from "cloudflare:workers"; +import { createModels } from "@earendil-works/pi-ai/models"; +import { createRegistry, Harness } from "@earendil-works/pi-durable"; import { PiHarness } from "agents/harness/pi"; import { Lifecycle } from "agents/lifecycle"; import { MCPClientManager } from "agents/mcp/client"; +import { createAI } from "agents/models/pi-ai"; import { Scheduler } from "agents/schedules"; export class Assistant extends DurableObject { + readonly ai = createAI({ binding: this.env.AI }); + readonly registry = createRegistry(); + readonly harness = new PiHarness({ - /* model and tools */ + harness: ({ storage, context, onWake }) => { + const models = createModels(); + models.setProvider(this.ai.provider); + return Harness.open( + storage, + { models, registry: this.registry, onWake }, + context, + ); + }, + defaults: { model: this.ai("@cf/zai-org/glm-5.3-flash") }, }); - readonly mcp = new MCPClientManager("assistant", "1.0.0", { env: this.env }); + readonly mcp = new MCPClientManager("assistant", "1.0.0"); readonly scheduler = new Scheduler({ callbacks: {} }); readonly lifecycle = Lifecycle.install(this) @@ -36,7 +51,7 @@ export class Assistant extends DurableObject { -In `agents@0.28.0`, `Lifecycle` is stable, along with the `Scheduler`, `State`, `WebSockets`, and `MCPClientManager` capabilities. The Lifecycle runs startup, routes requests, and owns the object's durable job queue and its single alarm, so the harness and its capabilities never overwrite each other's alarms, and their work resumes after eviction, deploys, and crashes. +In `agents@0.28.0`, `Lifecycle` is stable, along with the `Scheduler`, `State`, `WebSockets`, and `MCPClientManager` capabilities. The Lifecycle runs startup, routes requests, and owns the object's durable job queue and its single alarm, so the harness and its capabilities never overwrite each other's alarms, and their work resumes after eviction, deploys, and crashes. For a diagram of how the Durable Object, the Lifecycle, the harness, and capabilities fit together, refer to [Lifecycle](/agents/lifecycle/). You can pick from these harnesses: @@ -47,7 +62,7 @@ You can pick from these harnesses: | [Container harness](/agents/harnesses/container/), which runs Claude Code or Codex | `agents/harness/container` | Experimental | | [OpenCode](/agents/harnesses/opencode/) | `agents/harness/opencode` | Experimental | -To build your own, combine [Sessions](/agents/capabilities/sessions/), [Streams](/agents/capabilities/streams/), and [Tasks](/agents/capabilities/tasks/), or keep an outside agent's sessions in `HarnessStore` from `agents/harness/store`. +To build your own, combine [Sessions](/agents/capabilities/sessions/), [Streams](/agents/capabilities/streams/), and [Tasks](/agents/capabilities/tasks/), or keep an outside agent's sessions in `HarnessStore` from `agents/harness/store`. The [Codex harness example](https://github.com/cloudflare/agents/tree/main/examples/next/harnesses/codex) builds a harness this way: a turn loop derived from Codex, compiled to WebAssembly, running on Sessions, Streams, and Tasks. These capabilities ship in the `agents` package: From 824ae9bde99ed6273922be1bc48b4953ed148553 Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 27 Oct 2026 12:03:31 +0000 Subject: [PATCH 11/11] [Agents] Remove the AI SDK harness from the changelog --- .../changelog/agents/2026-10-27-agents-harnesses-lifecycle.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/content/changelog/agents/2026-10-27-agents-harnesses-lifecycle.mdx b/src/content/changelog/agents/2026-10-27-agents-harnesses-lifecycle.mdx index 787fd03888e..29d680bd41c 100644 --- a/src/content/changelog/agents/2026-10-27-agents-harnesses-lifecycle.mdx +++ b/src/content/changelog/agents/2026-10-27-agents-harnesses-lifecycle.mdx @@ -1,6 +1,6 @@ --- title: Run an agent harness on a Durable Object, with a stable Lifecycle -description: The Agents SDK Lifecycle, Scheduler, State, WebSockets, and MCP client are now stable. Run a harness such as Pi, the AI SDK, or a coding agent in a Container on any Durable Object, and plug in capabilities. +description: The Agents SDK Lifecycle, Scheduler, State, WebSockets, and MCP client are now stable. Run a harness such as Pi, OpenCode, or a coding agent in a Container on any Durable Object, and plug in capabilities. products: - agents - durable-objects @@ -58,7 +58,6 @@ You can pick from these harnesses: | Harness | Import | Status | | --------------------------------------------------------------------------------------------------- | -------------------------- | ------------ | | [Pi](/agents/harnesses/pi/) | `agents/harness/pi` | Beta | -| [AI SDK harness](/agents/harnesses/ai-sdk/) | `agents/harness/ai-sdk` | Experimental | | [Container harness](/agents/harnesses/container/), which runs Claude Code or Codex | `agents/harness/container` | Experimental | | [OpenCode](/agents/harnesses/opencode/) | `agents/harness/opencode` | Experimental |