History

Add spans over execution stack assembly

Name the shared executor, plugin, engine, and MCP server assembly steps so the post-auth execution gap is visible in traces.

Rhys Sullivan committed 4 months ago

Commit 2c1687a1 parent c926a3f5 files · +35 −16Browse files

Unified · Split

5 files
  1. apps/cloud/src/mcp/session-durable-object.ts +3 −1
  2. packages/core/api/src/server/execution-stack-middleware.ts +5 −2
  3. packages/core/api/src/server/execution-stack.ts +14 −8
  4. packages/core/api/src/server/mcp-build.ts +2 −0
  5. packages/core/api/src/server/scoped-executor.ts +11 −5
apps/cloud/src/mcp/session-durable-object.ts+3−1
@@ -271,7 +271,9 @@ export class McpSessionDOSqlite extends McpAgentSessionDOBase<Env, CloudSessionD · 270 unmodified lines
271271 // Build the description here so `executor.connections.list()` stays under
272272 // the DO startup span and the MCP SDK receives a concrete string instead
273273 // of invoking `engine.getDescription` across its async boundary.
274− const description = yield* buildExecuteDescription(executor);
274+ const description = yield* buildExecuteDescription(executor).pipe(
275+ Effect.withSpan("mcp.execute.description.build"),
276+ );
275277 const sessionElicitationMode = sessionMeta.elicitationMode ?? "model";
276278 const mcpServer = yield* createExecutorMcpServer({
277279 engine,
packages/core/api/src/server/execution-stack-middleware.ts+5−2
@@ -188,8 +188,11 @@ export const makeExecutionStackMiddleware = < · 187 unmodified lines
188188 // provider carried one. Absent slug -> the service stays unprovided and
189189 // the URL falls back to its bare, client-canonicalized form.
190190 const { executor, engine } = yield* resolved.organizationSlug !== undefined
191− ? stack.pipe(Effect.provideService(RequestOrgSlug, { slug: resolved.organizationSlug }))
192− : stack;
191+ ? stack.pipe(
192+ Effect.provideService(RequestOrgSlug, { slug: resolved.organizationSlug }),
193+ Effect.withSpan("executor.stack.http.resolve"),
194+ )
195+ : stack.pipe(Effect.withSpan("executor.stack.http.resolve"));
193196 return yield* httpEffect.pipe(
194197 Effect.provideService(AuthContext, auth),
195198 Effect.provideService(ExecutorService, executor),
packages/core/api/src/server/execution-stack.ts+14−8
@@ -108,13 +108,19 @@ export const makeExecutionStack = < · 107 unmodified lines
108108 organizationId,
109109 organizationName,
110110 { plugins: { mcpResource: options?.mcpResource } },
111+ ).pipe(Effect.withSpan("executor.stack.scoped_executor"));
112+ const codeExecutor = yield* CodeExecutorProvider.asEffect().pipe(
113+ Effect.withSpan("executor.stack.code_executor"),
111114 );
112− const codeExecutor = yield* CodeExecutorProvider;
113− const { decorate } = yield* EngineDecorator;
114− const engine = decorate(createExecutionEngine({ executor, codeExecutor }), {
115− accountId,
116− organizationId,
117− organizationName,
118− });
115+ const { decorate } = yield* EngineDecorator.asEffect().pipe(
116+ Effect.withSpan("executor.stack.decorator"),
117+ );
118+ const engine = yield* Effect.sync(() =>
119+ decorate(createExecutionEngine({ executor, codeExecutor }), {
120+ accountId,
121+ organizationId,
122+ organizationName,
123+ }),
124+ ).pipe(Effect.withSpan("executor.stack.engine.init"));
119125 return { executor, engine };
120− });
126+ }).pipe(Effect.withSpan("executor.stack.build"));
packages/core/api/src/server/mcp-build.ts+2−0
@@ -42,6 +42,7 @@ export const makeMcpBuildServer = · 41 unmodified lines
4242 makeExecutionStack(principal.accountId, principal.organizationId, principal.organizationName, {
4343 mcpResource: options?.resource,
4444 }).pipe(
45+ Effect.withSpan("mcp.execution_stack.build"),
4546 Effect.map(({ engine }) => engine),
4647 // Pin browser-handoff URLs to the principal's org slug when present;
4748 // absent slug leaves the service unprovided and the URL stays bare.
@@ -52,6 +53,7 @@ export const makeMcpBuildServer = · 4 unmodified lines
5253 Effect.mapError((cause) => new McpEngineBuildError({ cause })),
5354 Effect.flatMap((engine) =>
5455 createExecutorMcpServer({ engine, ...(options ?? {}) }).pipe(
56+ Effect.withSpan("mcp.server.create"),
5557 Effect.map((mcpServer) => ({ mcpServer, engine })),
5658 ),
5759 ),
packages/core/api/src/server/scoped-executor.ts+11−5
@@ -210,9 +210,13 @@ export const makeScopedExecutor = < · 209 unmodified lines
210210 options?: { readonly plugins?: PluginsProviderContext },
211211 ): Effect.Effect<Executor<TPlugins>, StorageFailure, DbProvider | PluginsProvider | HostConfig> =>
212212 Effect.gen(function* () {
213− const { db, blobs } = yield* DbProvider;
214− const { plugins: pluginsFactory } = yield* PluginsProvider;
215− const config = yield* HostConfig;
213+ const { db, blobs } = yield* DbProvider.asEffect().pipe(
214+ Effect.withSpan("executor.stack.db_provider"),
215+ );
216+ const { plugins: pluginsFactory } = yield* PluginsProvider.asEffect().pipe(
217+ Effect.withSpan("executor.stack.plugins_provider"),
218+ );
219+ const config = yield* HostConfig.asEffect().pipe(Effect.withSpan("executor.stack.host_config"));
216220 // Explicit config wins; otherwise fall back to the request origin if a host
217221 // provided one (HTTP middleware / MCP session DO). Stays `undefined` for
218222 // non-request callers — `coreTools.webBaseUrl` is optional and only the
@@ -248,7 +252,9 @@ export const makeScopedExecutor = < · 29 unmodified lines
248252 oauthCallbackPath: config.oauthCallbackPath,
249253 });
250254
251− const plugins = pluginsFactory(options?.plugins);
255+ const plugins = yield* Effect.sync(() => pluginsFactory(options?.plugins)).pipe(
256+ Effect.withSpan("executor.plugins.init"),
257+ );
252258 const hostedHttpOptions = {
253259 allowLocalNetwork: config.allowLocalNetwork,
254260 };
@@ -274,7 +280,7 @@ export const makeScopedExecutor = < · 19 unmodified lines
274280 orgSlug,
275281 includeProviders: config.exposeCredentialProviders ?? true,
276282 },
277− });
283+ }).pipe(Effect.withSpan("executor.stack.create_executor"));
278284 // The seam erases the plugin tuple type; the caller re-narrows via the
279285 // `TPlugins` phantom. Runtime shape is identical to a typed
280286 // `createExecutor({ plugins })` call.