releases.shpreview

Observability rewritten with diagnostics channels; keepAlive prevents DO eviction

v0.7.0

3 featuresThis release3 featuresNew capabilitiesAI-tallied from the release notes
From the original release noteView original ↗

The latest release of the Agents SDK ↗ rewrites observability from scratch with diagnostics_channel, adds keepAlive() to prevent Durable Object eviction during long-running work, and introduces waitForMcpConnections so MCP tools are always available when onChatMessage runs.

Observability rewrite

The previous observability system used console.log() with a custom Observability.emit() interface. v0.7.0 replaces it with structured events published to diagnostics channels — silent by default, zero overhead when nobody is listening.

Every event has a type, payload, and timestamp. Events are routed to seven named channels:

Channel

Event types

agents:state

state:update

agents:rpc

rpc, rpc:error

agents:message

message:request, message:response, message:clear, message:cancel, message:error, tool:result, tool:approval

agents:schedule

schedule:create, schedule:execute, schedule:cancel, schedule:retry, schedule:error, queue:retry, queue:error

agents:lifecycle

connect, destroy

agents:workflow

workflow:start, workflow:event, workflow:approved, workflow:rejected, workflow:terminated, workflow:paused, workflow:resumed, workflow:restarted

agents:mcp

mcp:client:preconnect, mcp:client:connect, mcp:client:authorize, mcp:client:discover

Use the typed subscribe() helper from agents/observability for type-safe access:

<span class="line"><span class="nb-shiki-1itgoe">import</span><span class="nb-shiki-140thh"> { subscribe } </span><span class="nb-shiki-1itgoe">from</span><span class="nb-shiki-mdbnqw"> "agents/observability"</span><span class="nb-shiki-140thh">;</span></span>
<span class="line"></span>
<span class="line"><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> unsub</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1t8gfj"> subscribe</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-mdbnqw">"rpc"</span><span class="nb-shiki-140thh">, (</span><span class="nb-shiki-1jdh33">event</span><span class="nb-shiki-140thh">) </span><span class="nb-shiki-1itgoe">=></span><span class="nb-shiki-140thh"> {</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	if</span><span class="nb-shiki-140thh"> (event.type </span><span class="nb-shiki-1itgoe">===</span><span class="nb-shiki-mdbnqw"> "rpc"</span><span class="nb-shiki-140thh">) {</span></span>
<span class="line"><span class="nb-shiki-140thh">		console.</span><span class="nb-shiki-1t8gfj">log</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-mdbnqw">`RPC call: ${</span><span class="nb-shiki-140thh">event</span><span class="nb-shiki-mdbnqw">.</span><span class="nb-shiki-140thh">payload</span><span class="nb-shiki-mdbnqw">.</span><span class="nb-shiki-140thh">method</span><span class="nb-shiki-mdbnqw">}`</span><span class="nb-shiki-140thh">);</span></span>
<span class="line"><span class="nb-shiki-140thh">	}</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	if</span><span class="nb-shiki-140thh"> (event.type </span><span class="nb-shiki-1itgoe">===</span><span class="nb-shiki-mdbnqw"> "rpc:error"</span><span class="nb-shiki-140thh">) {</span></span>
<span class="line"><span class="nb-shiki-140thh">		console.</span><span class="nb-shiki-1t8gfj">error</span><span class="nb-shiki-140thh">(</span></span>
<span class="line"><span class="nb-shiki-mdbnqw">			`RPC failed: ${</span><span class="nb-shiki-140thh">event</span><span class="nb-shiki-mdbnqw">.</span><span class="nb-shiki-140thh">payload</span><span class="nb-shiki-mdbnqw">.</span><span class="nb-shiki-140thh">method</span><span class="nb-shiki-mdbnqw">} — ${</span><span class="nb-shiki-140thh">event</span><span class="nb-shiki-mdbnqw">.</span><span class="nb-shiki-140thh">payload</span><span class="nb-shiki-mdbnqw">.</span><span class="nb-shiki-140thh">error</span><span class="nb-shiki-mdbnqw">}`</span><span class="nb-shiki-140thh">,</span></span>
<span class="line"><span class="nb-shiki-140thh">		);</span></span>
<span class="line"><span class="nb-shiki-140thh">	}</span></span>
<span class="line"><span class="nb-shiki-140thh">});</span></span>
<span class="line"></span>
<span class="line"><span class="nb-shiki-21nrsd">// Clean up when done</span></span>
<span class="line"><span class="nb-shiki-1t8gfj">unsub</span><span class="nb-shiki-140thh">();</span></span>
<span class="line"><span class="nb-shiki-1itgoe">import</span><span class="nb-shiki-140thh"> { subscribe } </span><span class="nb-shiki-1itgoe">from</span><span class="nb-shiki-mdbnqw"> "agents/observability"</span><span class="nb-shiki-140thh">;</span></span>
<span class="line"></span>
<span class="line"><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> unsub</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1t8gfj"> subscribe</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-mdbnqw">"rpc"</span><span class="nb-shiki-140thh">, (</span><span class="nb-shiki-1jdh33">event</span><span class="nb-shiki-140thh">) </span><span class="nb-shiki-1itgoe">=></span><span class="nb-shiki-140thh"> {</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	if</span><span class="nb-shiki-140thh"> (event.type </span><span class="nb-shiki-1itgoe">===</span><span class="nb-shiki-mdbnqw"> "rpc"</span><span class="nb-shiki-140thh">) {</span></span>
<span class="line"><span class="nb-shiki-140thh">		console.</span><span class="nb-shiki-1t8gfj">log</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-mdbnqw">`RPC call: ${</span><span class="nb-shiki-140thh">event</span><span class="nb-shiki-mdbnqw">.</span><span class="nb-shiki-140thh">payload</span><span class="nb-shiki-mdbnqw">.</span><span class="nb-shiki-140thh">method</span><span class="nb-shiki-mdbnqw">}`</span><span class="nb-shiki-140thh">);</span></span>
<span class="line"><span class="nb-shiki-140thh">	}</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	if</span><span class="nb-shiki-140thh"> (event.type </span><span class="nb-shiki-1itgoe">===</span><span class="nb-shiki-mdbnqw"> "rpc:error"</span><span class="nb-shiki-140thh">) {</span></span>
<span class="line"><span class="nb-shiki-140thh">		console.</span><span class="nb-shiki-1t8gfj">error</span><span class="nb-shiki-140thh">(</span></span>
<span class="line"><span class="nb-shiki-mdbnqw">			`RPC failed: ${</span><span class="nb-shiki-140thh">event</span><span class="nb-shiki-mdbnqw">.</span><span class="nb-shiki-140thh">payload</span><span class="nb-shiki-mdbnqw">.</span><span class="nb-shiki-140thh">method</span><span class="nb-shiki-mdbnqw">} — ${</span><span class="nb-shiki-140thh">event</span><span class="nb-shiki-mdbnqw">.</span><span class="nb-shiki-140thh">payload</span><span class="nb-shiki-mdbnqw">.</span><span class="nb-shiki-140thh">error</span><span class="nb-shiki-mdbnqw">}`</span><span class="nb-shiki-140thh">,</span></span>
<span class="line"><span class="nb-shiki-140thh">		);</span></span>
<span class="line"><span class="nb-shiki-140thh">	}</span></span>
<span class="line"><span class="nb-shiki-140thh">});</span></span>
<span class="line"></span>
<span class="line"><span class="nb-shiki-21nrsd">// Clean up when done</span></span>
<span class="line"><span class="nb-shiki-1t8gfj">unsub</span><span class="nb-shiki-140thh">();</span></span>

In production, all diagnostics channel messages are automatically forwarded to Tail Workers — no subscription code needed in the agent itself:

<span class="line"><span class="nb-shiki-1itgoe">export</span><span class="nb-shiki-1itgoe"> default</span><span class="nb-shiki-140thh"> {</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	async</span><span class="nb-shiki-1t8gfj"> tail</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1jdh33">events</span><span class="nb-shiki-140thh">) {</span></span>
<span class="line"><span class="nb-shiki-1itgoe">		for</span><span class="nb-shiki-140thh"> (</span><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> event</span><span class="nb-shiki-1itgoe"> of</span><span class="nb-shiki-140thh"> events) {</span></span>
<span class="line"><span class="nb-shiki-1itgoe">			for</span><span class="nb-shiki-140thh"> (</span><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> msg</span><span class="nb-shiki-1itgoe"> of</span><span class="nb-shiki-140thh"> event.diagnosticsChannelEvents) {</span></span>
<span class="line"><span class="nb-shiki-21nrsd">				// msg.channel is "agents:rpc", "agents:workflow", etc.</span></span>
<span class="line"><span class="nb-shiki-140thh">				console.</span><span class="nb-shiki-1t8gfj">log</span><span class="nb-shiki-140thh">(msg.timestamp, msg.channel, msg.message);</span></span>
<span class="line"><span class="nb-shiki-140thh">			}</span></span>
<span class="line"><span class="nb-shiki-140thh">		}</span></span>
<span class="line"><span class="nb-shiki-140thh">	},</span></span>
<span class="line"><span class="nb-shiki-140thh">};</span></span>
<span class="line"><span class="nb-shiki-1itgoe">export</span><span class="nb-shiki-1itgoe"> default</span><span class="nb-shiki-140thh"> {</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	async</span><span class="nb-shiki-1t8gfj"> tail</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1jdh33">events</span><span class="nb-shiki-140thh">) {</span></span>
<span class="line"><span class="nb-shiki-1itgoe">		for</span><span class="nb-shiki-140thh"> (</span><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> event</span><span class="nb-shiki-1itgoe"> of</span><span class="nb-shiki-140thh"> events) {</span></span>
<span class="line"><span class="nb-shiki-1itgoe">			for</span><span class="nb-shiki-140thh"> (</span><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> msg</span><span class="nb-shiki-1itgoe"> of</span><span class="nb-shiki-140thh"> event.diagnosticsChannelEvents) {</span></span>
<span class="line"><span class="nb-shiki-21nrsd">				// msg.channel is "agents:rpc", "agents:workflow", etc.</span></span>
<span class="line"><span class="nb-shiki-140thh">				console.</span><span class="nb-shiki-1t8gfj">log</span><span class="nb-shiki-140thh">(msg.timestamp, msg.channel, msg.message);</span></span>
<span class="line"><span class="nb-shiki-140thh">			}</span></span>
<span class="line"><span class="nb-shiki-140thh">		}</span></span>
<span class="line"><span class="nb-shiki-140thh">	},</span></span>
<span class="line"><span class="nb-shiki-140thh">};</span></span>

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 Observability documentation.

keepAlive() and keepAliveWhile()

Durable Objects are evicted after a period of inactivity (typically 70-140 seconds with no incoming requests, WebSocket messages, or alarms). During long-running operations — streaming LLM responses, waiting on external APIs, running multi-step computations — the agent can be evicted mid-flight.

keepAlive() prevents this by creating a 30-second heartbeat schedule. The alarm firing resets the inactivity timer. Returns a disposer function that cancels the heartbeat when called.

<span class="line"><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> dispose</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-dzsirb"> this</span><span class="nb-shiki-140thh">.</span><span class="nb-shiki-1t8gfj">keepAlive</span><span class="nb-shiki-140thh">();</span></span>
<span class="line"><span class="nb-shiki-1itgoe">try</span><span class="nb-shiki-140thh"> {</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	const</span><span class="nb-shiki-dzsirb"> result</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-1t8gfj"> longRunningComputation</span><span class="nb-shiki-140thh">();</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	await</span><span class="nb-shiki-1t8gfj"> sendResults</span><span class="nb-shiki-140thh">(result);</span></span>
<span class="line"><span class="nb-shiki-140thh">} </span><span class="nb-shiki-1itgoe">finally</span><span class="nb-shiki-140thh"> {</span></span>
<span class="line"><span class="nb-shiki-1t8gfj">	dispose</span><span class="nb-shiki-140thh">();</span></span>
<span class="line"><span class="nb-shiki-140thh">}</span></span>
<span class="line"><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> dispose</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-dzsirb"> this</span><span class="nb-shiki-140thh">.</span><span class="nb-shiki-1t8gfj">keepAlive</span><span class="nb-shiki-140thh">();</span></span>
<span class="line"><span class="nb-shiki-1itgoe">try</span><span class="nb-shiki-140thh"> {</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	const</span><span class="nb-shiki-dzsirb"> result</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-1t8gfj"> longRunningComputation</span><span class="nb-shiki-140thh">();</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	await</span><span class="nb-shiki-1t8gfj"> sendResults</span><span class="nb-shiki-140thh">(result);</span></span>
<span class="line"><span class="nb-shiki-140thh">} </span><span class="nb-shiki-1itgoe">finally</span><span class="nb-shiki-140thh"> {</span></span>
<span class="line"><span class="nb-shiki-1t8gfj">	dispose</span><span class="nb-shiki-140thh">();</span></span>
<span class="line"><span class="nb-shiki-140thh">}</span></span>

keepAliveWhile() wraps an async function with automatic cleanup — the heartbeat starts before the function runs and stops when it completes:

<span class="line"><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> result</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-dzsirb"> this</span><span class="nb-shiki-140thh">.</span><span class="nb-shiki-1t8gfj">keepAliveWhile</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1itgoe">async</span><span class="nb-shiki-140thh"> () </span><span class="nb-shiki-1itgoe">=></span><span class="nb-shiki-140thh"> {</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	const</span><span class="nb-shiki-dzsirb"> data</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-1t8gfj"> longRunningComputation</span><span class="nb-shiki-140thh">();</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	return</span><span class="nb-shiki-140thh"> data;</span></span>
<span class="line"><span class="nb-shiki-140thh">});</span></span>
<span class="line"><span class="nb-shiki-1itgoe">const</span><span class="nb-shiki-dzsirb"> result</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-dzsirb"> this</span><span class="nb-shiki-140thh">.</span><span class="nb-shiki-1t8gfj">keepAliveWhile</span><span class="nb-shiki-140thh">(</span><span class="nb-shiki-1itgoe">async</span><span class="nb-shiki-140thh"> () </span><span class="nb-shiki-1itgoe">=></span><span class="nb-shiki-140thh"> {</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	const</span><span class="nb-shiki-dzsirb"> data</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-1itgoe"> await</span><span class="nb-shiki-1t8gfj"> longRunningComputation</span><span class="nb-shiki-140thh">();</span></span>
<span class="line"><span class="nb-shiki-1itgoe">	return</span><span class="nb-shiki-140thh"> data;</span></span>
<span class="line"><span class="nb-shiki-140thh">});</span></span>

Key details:

  • Multiple concurrent callers — Each keepAlive() call returns an independent disposer. Disposing one does not affect others.
  • AIChatAgent built-inAIChatAgent automatically calls keepAlive() during streaming responses. You do not need to add it yourself.
  • Uses the scheduling system — The heartbeat does not conflict with your own schedules. It shows up in getSchedules() if you need to inspect it.

Note

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.

waitForMcpConnections

AIChatAgent now waits for MCP server connections to settle before calling onChatMessage. This ensures this.mcp.getAITools() returns the full set of tools, especially after Durable Object hibernation when connections are being restored in the background.

<span class="line"><span class="nb-shiki-1itgoe">export</span><span class="nb-shiki-1itgoe"> class</span><span class="nb-shiki-1t8gfj"> ChatAgent</span><span class="nb-shiki-1itgoe"> extends</span><span class="nb-shiki-1t8gfj"> AIChatAgent</span><span class="nb-shiki-140thh"> {</span></span>
<span class="line"><span class="nb-shiki-21nrsd">	// Default — waits up to 10 seconds</span></span>
<span class="line"><span class="nb-shiki-21nrsd">	// waitForMcpConnections = { timeout: 10_000 };</span></span>
<span class="line"></span>
<span class="line"><span class="nb-shiki-21nrsd">	// Wait forever</span></span>
<span class="line"><span class="nb-shiki-1jdh33">	waitForMcpConnections</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-dzsirb"> true</span><span class="nb-shiki-140thh">;</span></span>
<span class="line"></span>
<span class="line"><span class="nb-shiki-21nrsd">	// Disable waiting</span></span>
<span class="line"><span class="nb-shiki-1jdh33">	waitForMcpConnections</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-dzsirb"> false</span><span class="nb-shiki-140thh">;</span></span>
<span class="line"><span class="nb-shiki-140thh">}</span></span>
<span class="line"><span class="nb-shiki-1itgoe">export</span><span class="nb-shiki-1itgoe"> class</span><span class="nb-shiki-1t8gfj"> ChatAgent</span><span class="nb-shiki-1itgoe"> extends</span><span class="nb-shiki-1t8gfj"> AIChatAgent</span><span class="nb-shiki-140thh"> {</span></span>
<span class="line"><span class="nb-shiki-21nrsd">	// Default — waits up to 10 seconds</span></span>
<span class="line"><span class="nb-shiki-21nrsd">	// waitForMcpConnections = { timeout: 10_000 };</span></span>
<span class="line"></span>
<span class="line"><span class="nb-shiki-21nrsd">	// Wait forever</span></span>
<span class="line"><span class="nb-shiki-1jdh33">	waitForMcpConnections</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-dzsirb"> true</span><span class="nb-shiki-140thh">;</span></span>
<span class="line"></span>
<span class="line"><span class="nb-shiki-21nrsd">	// Disable waiting</span></span>
<span class="line"><span class="nb-shiki-1jdh33">	waitForMcpConnections</span><span class="nb-shiki-1itgoe"> =</span><span class="nb-shiki-dzsirb"> false</span><span class="nb-shiki-140thh">;</span></span>
<span class="line"><span class="nb-shiki-140thh">}</span></span>

Value

Behavior

{ timeout: 10_000 }

Wait up to 10 seconds (default)

{ timeout: N }

Wait up to N milliseconds

true

Wait indefinitely until all connections ready

false

Do not wait (old behavior before 0.2.0)

For lower-level control, call this.mcp.waitForConnections() directly inside onChatMessage instead.

Other improvements
  • MCP deduplication by name and URLaddMcpServer with HTTP transport now deduplicates on both server name and URL. Calling it with the same name but a different URL creates a new connection. URLs are normalized before comparison (trailing slashes, default ports, hostname case).
  • callbackHost optional for non-OAuth serversaddMcpServer no longer requires callbackHost when connecting to MCP servers that do not use OAuth.
  • MCP URL security — Server URLs are validated before connection to prevent SSRF. Private IP ranges, loopback addresses, link-local addresses, and cloud metadata endpoints are blocked.
  • Custom denial messagesaddToolOutput now supports state: "output-error" with errorText for custom denial messages in human-in-the-loop tool approval flows.
  • requestId in chat optionsonChatMessage options now include a requestId for logging and correlating events.
Upgrade

To update to the latest version:

<span class="line"><span class="nb-shiki-1t8gfj">npm</span><span class="nb-shiki-mdbnqw"> i</span><span class="nb-shiki-mdbnqw"> agents@latest</span><span class="nb-shiki-mdbnqw"> @cloudflare/ai-chat@latest</span></span>

Fetched July 22, 2026

Observability rewritten with diagnostics channels;… — releases.sh