Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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