Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/tidy-agent-traces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"agents": patch
"@cloudflare/ai-chat": patch
"@cloudflare/think": patch
---

Reduce SDK trace noise by consolidating initialization, chat setup, and fiber lifecycle spans around semantic agent operations.
17 changes: 8 additions & 9 deletions docs/agents/observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,15 +262,14 @@ These events are emitted by `AIChatAgent` from `@cloudflare/ai-chat`. They track

## Agent initialization span

When Worker traces are enabled, every `Agent` constructor runs its setup —
method wrapping, schema creation, and MCP client manager initialization —
inside an `agent_initialization` span. Constructor-time child spans group under
this one stable parent instead of appearing as top-level clutter. The span
carries `cloudflare.agents.agent.name` (the agent class),
`cloudflare.agents.agent.id` (the named instance, omitted when the name is not
yet readable during construction), and `cloudflare.agents.operation.name`
(`agent_initialization`). Like the rest of the tracing in this package, it is a
no-op when the runtime has no native tracing capability.
When Worker traces are enabled, the base Agent startup runs inside one
`agent_initialization` span. Internal state restoration, recovery, and user
startup hooks do not emit additional setup spans. The span carries
`cloudflare.agents.agent.name` (the agent class),
`cloudflare.agents.agent.id` (the named instance), and
`cloudflare.agents.operation.name` (`agent_initialization`). Like the rest of
the tracing in this package, it is a no-op when the runtime has no native
tracing capability.

## AI SDK tracing

Expand Down
204 changes: 57 additions & 147 deletions packages/agents/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1958,14 +1958,6 @@ export class Agent<
throw new SqlError(query, e);
}
}
private _schemaInitialization:
| {
previousVersion: number;
currentVersion: number;
migrated: boolean;
}
| undefined;

/**
* Create all internal tables and run migrations if needed.
* Called by the constructor on every wake. Idempotent — skips DDL when
Expand Down Expand Up @@ -2324,55 +2316,25 @@ export class Agent<
VALUES (${SCHEMA_VERSION_ROW_ID}, ${String(CURRENT_SCHEMA_VERSION)})
`;
}

this._schemaInitialization = {
previousVersion: schemaVersion,
currentVersion: CURRENT_SCHEMA_VERSION,
migrated: schemaVersion < CURRENT_SCHEMA_VERSION
};
}

constructor(ctx: AgentContext, env: Env) {
super(ctx, env);

this.mcp = this._withAgentSpan(
"agent_initialization",
"initialization",
{},
(update) => {
if (!wrappedClasses.has(this.constructor)) {
// Auto-wrap custom methods with agent context
this._autoWrapCustomMethods();
wrappedClasses.add(this.constructor);
}
if (!wrappedClasses.has(this.constructor)) {
// Auto-wrap custom methods with agent context
this._autoWrapCustomMethods();
wrappedClasses.add(this.constructor);
}

this._withAgentSpan(
"initialize_agent_storage",
"initialization",
{},
(updateStorage) => {
this._ensureSchema();
const schemaAttributes = {
"cloudflare.agents.schema.version.previous":
this._schemaInitialization?.previousVersion,
"cloudflare.agents.schema.version.current":
this._schemaInitialization?.currentVersion,
"cloudflare.agents.schema.migrated":
this._schemaInitialization?.migrated
};
updateStorage(schemaAttributes);
update(schemaAttributes);
}
);
this._ensureSchema();

// Initialize MCPClientManager AFTER tables are created
return new MCPClientManager(this._ParentClass.name, "0.0.1", {
storage: this.ctx.storage,
createAuthProvider: (callbackUrl) =>
this.createMcpOAuthProvider(callbackUrl)
});
}
);
// Initialize MCPClientManager AFTER tables are created
this.mcp = new MCPClientManager(this._ParentClass.name, "0.0.1", {
storage: this.ctx.storage,
createAuthProvider: (callbackUrl) =>
this.createMcpOAuthProvider(callbackUrl)
});

// Broadcast server state whenever MCP state changes (register, connect, OAuth, remove, etc.)
this._disposables.add(
Expand Down Expand Up @@ -2731,67 +2693,46 @@ export class Agent<
email: undefined
},
async () => {
await this._withAgentSpan(
"restore_agent_state",
"startup",
{},
async () => {
// Hydrate _isFacet from persistent storage so the flag
// survives hibernation (the DO constructor resets it to false).
const isFacet =
await this.ctx.storage.get<boolean>("cf_agents_is_facet");
if (isFacet) this._isFacet = true;

const storedFacetName = await this.ctx.storage.get<string>(
"cf_agents_facet_name"
);
if (typeof storedFacetName === "string") {
this._facetName = storedFacetName;
}

const storedParentPath = await this.ctx.storage.get<
Array<{ className: string; name: string }>
>("cf_agents_parent_path");
if (isValidParentPath(storedParentPath)) {
this._parentPath = storedParentPath;
}
try {
await this._cf_hydrateSubAgentConnectionsFromRoot();
} catch (error) {
console.warn(
"[Agent] Unable to hydrate sub-agent WebSocket connections:",
error
);
}
}
// Hydrate _isFacet from persistent storage so the flag survives
// hibernation (the DO constructor resets it to false).
const isFacet =
await this.ctx.storage.get<boolean>("cf_agents_is_facet");
if (isFacet) this._isFacet = true;

const storedFacetName = await this.ctx.storage.get<string>(
"cf_agents_facet_name"
);
if (typeof storedFacetName === "string") {
this._facetName = storedFacetName;
}

const storedParentPath = await this.ctx.storage.get<
Array<{ className: string; name: string }>
>("cf_agents_parent_path");
if (isValidParentPath(storedParentPath)) {
this._parentPath = storedParentPath;
}
try {
await this._cf_hydrateSubAgentConnectionsFromRoot();
} catch (error) {
console.warn(
"[Agent] Unable to hydrate sub-agent WebSocket connections:",
error
);
}

await this._tryCatch(async () => {
// Restore MCP connections before fiber/chat recovery so recovered
// turns see MCP tools. Restored connections re-advertise the
// capabilities persisted from the previous session; the handlers
// behind them attach when onStart() configures them.
await this._withAgentSpan(
"restore_mcp_connections",
"startup",
{},
async () => {
await this.mcp.restoreConnectionsFromStorage(this.name);
await this._restoreRpcMcpServers();
this.broadcastMcpServers();
}
);
await this.mcp.restoreConnectionsFromStorage(this.name);
await this._restoreRpcMcpServers();
this.broadcastMcpServers();

const startupAgentToolRunIds = await this._withAgentSpan(
"recover_agent_work",
"startup",
{},
async () => {
this._checkOrphanedWorkflows();
await this._checkRunFibers();
return this._agentToolRunRecoveryRunIds();
}
);
this._checkOrphanedWorkflows();
await this._checkRunFibers();
const startupAgentToolRunIds = this._agentToolRunRecoveryRunIds();
update({
"cloudflare.agents.start.facet": this._isFacet,
"cloudflare.agents.recovery.agent_tools.count":
Expand All @@ -2812,12 +2753,7 @@ export class Agent<
this._warnedScheduleInOnStart.clear();
let result: Awaited<ReturnType<typeof _onStart>>;
try {
result = await this._withAgentSpan(
"run_user_on_start",
"startup",
{},
() => _onStart(props)
);
result = await _onStart(props);
} finally {
this._insideOnStart = false;
}
Expand Down Expand Up @@ -2859,7 +2795,7 @@ export class Agent<
);
};
this.onStart = (props?: Props) =>
this._withAgentSpan("agent_start", "startup", {}, (update) =>
this._withAgentSpan("agent_initialization", "startup", {}, (update) =>
startAgent(props, update)
);
}
Expand Down Expand Up @@ -5680,26 +5616,16 @@ export class Agent<

const writeSnapshot = (data: unknown) => {
const snapshot = JSON.stringify(data);
this._withAgentSpan(
"persist_fiber_snapshot",
"fiber",
{
"cloudflare.agents.fiber.id": id,
"cloudflare.agents.fiber.name": name
},
() => {
this.sql`
UPDATE cf_agents_runs SET snapshot = ${snapshot}
WHERE id = ${id}
`;
if (options?.managed) {
this.sql`
UPDATE cf_agents_fibers SET snapshot = ${snapshot}
WHERE fiber_id = ${id}
`;
}
}
);
this.sql`
UPDATE cf_agents_runs SET snapshot = ${snapshot}
WHERE id = ${id}
`;
if (options?.managed) {
this.sql`
UPDATE cf_agents_fibers SET snapshot = ${snapshot}
WHERE fiber_id = ${id}
`;
}
};

let root: RootFacetRpcSurface | undefined;
Expand Down Expand Up @@ -5746,17 +5672,7 @@ export class Agent<
}
} finally {
this._runFiberActiveFibers.delete(id);
this._withAgentSpan(
"finalize_fiber",
"fiber",
{
"cloudflare.agents.fiber.id": id,
"cloudflare.agents.fiber.name": name
},
() => {
this.sql`DELETE FROM cf_agents_runs WHERE id = ${id}`;
}
);
this.sql`DELETE FROM cf_agents_runs WHERE id = ${id}`;
dispose();
if (root && registeredFacetRun) {
try {
Expand Down Expand Up @@ -6486,12 +6402,6 @@ export class Agent<
}

private async _scheduleNextAlarm(): Promise<void> {
await this._withAgentSpan("schedule_agent_alarm", "alarm", {}, () =>
this._scheduleNextAlarmBody()
);
}

private async _scheduleNextAlarmBody(): Promise<void> {
// A pending destroy (#1625) owns the alarm: keep it armed immediately so
// teardown lands, and never let the "no work pending" branch below
// delete it out from under `_cf_scheduleDestroy`.
Expand Down
Loading
Loading