Skip to content
Cloudflare Docs

Observability

Agents emit structured events for every significant operation — RPC calls, state changes, schedule execution, workflow transitions, MCP connections, and more. These events are published to diagnostics channels and are silent by default (zero overhead when nobody is listening).

Event structure

Every event has these fields:

TypeScript
{
type: "rpc", // what happened
agent: "MyAgent", // which agent class emitted it
name: "user-123", // which agent instance (Durable Object name)
payload: { method: "getWeather" }, // details
timestamp: 1758005142787 // when (ms since epoch)
}

agent and name identify the source agent — agent is the class name and name is the Durable Object instance name.

Channels

Events are routed to eight named channels based on their type:

ChannelEvent typesDescription
agents:statestate:updateState sync events
agents:rpcrpc, rpc:errorRPC method calls and failures
agents:messagemessage:request, message:response, message:clear, message:cancel, message:error, tool:result, tool:approvalChat message and tool lifecycle
agents:scheduleschedule:create, schedule:execute, schedule:cancel, schedule:retry, schedule:error, queue:create, queue:retry, queue:errorScheduled and queued task lifecycle
agents:lifecycleconnect, disconnect, destroyAgent connection and teardown
agents:workflowworkflow:start, workflow:event, workflow:approved, workflow:rejected, workflow:terminated, workflow:paused, workflow:resumed, workflow:restartedWorkflow state transitions
agents:mcpmcp:client:preconnect, mcp:client:connect, mcp:client:authorize, mcp:client:discoverMCP client operations
agents:emailemail:receive, email:replyEmail processing

Subscribing to events

Typed subscribe helper

The subscribe() function from agents/observability provides type-safe access to events on a specific channel:

JavaScript
import { subscribe } from "agents/observability";
const unsub = subscribe("rpc", (event) => {
if (event.type === "rpc") {
console.log(`RPC call: ${event.payload.method}`);
}
if (event.type === "rpc:error") {
console.error(
`RPC failed: ${event.payload.method}${event.payload.error}`,
);
}
});
// Clean up when done
unsub();

The callback is fully typed — event is narrowed to only the event types that flow through that channel.

Raw diagnostics_channel

You can also subscribe directly using the Node.js API:

JavaScript
import { subscribe } from "node:diagnostics_channel";
subscribe("agents:schedule", (event) => {
console.log(event);
});

Tail Workers (production)

In production, all diagnostics channel messages are automatically forwarded to Tail Workers. No subscription code is needed in the agent itself — attach a Tail Worker and access events via event.diagnosticsChannelEvents:

JavaScript
export default {
async tail(events) {
for (const event of events) {
for (const msg of event.diagnosticsChannelEvents) {
// msg.channel is "agents:rpc", "agents:workflow", etc.
// msg.message is the typed event payload
console.log(msg.timestamp, msg.channel, msg.message);
}
}
},
};

This gives you structured, filterable observability in production with zero overhead in the agent hot path.

Custom observability

You can override the default implementation by providing your own Observability interface:

JavaScript
import { Agent } from "agents";
const myObservability = {
emit(event) {
// Send to your logging service, filter events, etc.
if (event.type === "rpc:error") {
console.error(event.payload.method, event.payload.error);
}
},
};
class MyAgent extends Agent {
observability = myObservability;
}

Set observability to undefined to disable all event emission:

JavaScript
import { Agent } from "agents";
class MyAgent extends Agent {
observability = undefined;
}

Event reference

RPC events

TypePayloadWhen
rpc{ method, streaming? }A @callable method is invoked
rpc:error{ method, error }A @callable method throws

State events

TypePayloadWhen
state:update{}setState() is called

Message and tool events (AIChatAgent)

These events are emitted by AIChatAgent from @cloudflare/ai-chat. They track the chat message lifecycle, including client-side tool interactions.

TypePayloadWhen
message:request{}A chat message is received
message:response{}A chat response stream completes
message:clear{}Chat history is cleared
message:cancel{ requestId }A streaming request is cancelled
message:error{ error }A chat stream fails
tool:result{ toolCallId, toolName }A client tool result is received
tool:approval{ toolCallId, approved }A tool call is approved or rejected

Schedule and queue events

TypePayloadWhen
schedule:create{ callback, id }A schedule is created
schedule:execute{ callback, id }A scheduled callback starts
schedule:cancel{ callback, id }A schedule is cancelled
schedule:retry{ callback, id, attempt, maxAttempts }A scheduled callback is retried
schedule:error{ callback, id, error, attempts }A scheduled callback fails after all retries
queue:create{ callback, id }A task is enqueued
queue:retry{ callback, id, attempt, maxAttempts }A queued callback is retried
queue:error{ callback, id, error, attempts }A queued callback fails after all retries

Lifecycle events

TypePayloadWhen
connect{ connectionId }A WebSocket connection is established
disconnect{ connectionId, code, reason }A WebSocket connection is closed
destroy{}The agent is destroyed

Workflow events

TypePayloadWhen
workflow:start{ workflowId, workflowName? }A workflow instance is started
workflow:event{ workflowId, eventType? }An event is sent to a workflow
workflow:approved{ workflowId, reason? }A workflow is approved
workflow:rejected{ workflowId, reason? }A workflow is rejected
workflow:terminated{ workflowId, workflowName? }A workflow is terminated
workflow:paused{ workflowId, workflowName? }A workflow is paused
workflow:resumed{ workflowId, workflowName? }A workflow is resumed
workflow:restarted{ workflowId, workflowName? }A workflow is restarted

MCP events

TypePayloadWhen
mcp:client:preconnect{ serverId }Before connecting to an MCP server
mcp:client:connect{ url, transport, state, error? }An MCP connection attempt completes or fails
mcp:client:authorize{ serverId, authUrl, clientId? }An MCP OAuth flow begins
mcp:client:discover{ url?, state?, error?, capability? }MCP capability discovery succeeds or fails

Email events

TypePayloadWhen
email:receive{ from, to, subject? }An email is received
email:reply{ from, to, subject? }A reply email is sent

Next steps